線程池的使用

一、簡述

在開發中,頻繁的創建和銷燬一個線程,是極耗資源的,爲此創建一個可重用指定線程數的線程池,以共享的無界隊列方式來運行這些線程,可以有效的規劃線程的使用。
線程池顧名思義,也就是線程的集合,在java中大致有這幾種線程池:

  •     newSingleThreadExecutor  創建一個單線程化的線程池, 它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行,可以控制線程的執行順序
  •     newFixedThreadPool  創建一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待,當創建的線程池數量爲1的時候。也類似於單線程化的線程池,當爲1的時候,也可控制線程的執行順序
  •     newCachedThreadPool  創建一個可緩存線程池,如果線程池長度超過需要的線程數量,可靈活回收空閒線程,若無可回收,則新建線程
  •     newScheduledThreadPool  創建一個定長線程池,支持定時及週期性任務執行,可以作一個定時器使用


    
二、用法案例

說太多原理,講太多種方式,還沒讓人明白怎麼用也是瞎BB。下面直接通過完整代碼講述newFixedThreadPool方式創建一個定長線程池的用法,然後再簡述下其它用法和原理就瞭解了。
代碼:

public class ThreadPoolTest {

	public static void main(String[] args) {
		try {
			//調用測試線程池的使用
			ThreadPoolTest threadPoolTest = new ThreadPoolTest();
			threadPoolTest.callService();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

	}
	
	/**
	 * 假設有8條數據需要分別放置在不同的任務線程中處理
	 * @throws InterruptedException
	 */
	public void callService() throws InterruptedException {
		//創建一個定長爲3的線程池
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
        
        for (int i = 1; i <= 8; i++){
        	Thread.sleep(100);//稍作等待
        	System.out.println("-------第"+i+"次for循環  -------");
        	
            int sNum = i;
            fixedThreadPool.execute(new Runnable() {//線程池開啓線程任務
                public void run() {
                	SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
            		
            		System.out.println("現在是線程\""+Thread.currentThread().getName()+"\",他在操作第"+sNum+"條數據----開始:"+sdf.format(new Date()));
            		
            		try { 
            			Thread.sleep(5000);//假設,處理一條數據需要5秒
					} catch (InterruptedException e) { e.printStackTrace(); }
            		
        			System.out.println("現在是線程\""+Thread.currentThread().getName()+"\",他在操作第"+sNum+"條數據----結束:"+sdf.format(new Date()));
                }
            });
        }
        
        System.out.println("-------方法結束 -------");  
	}

}


輸出:

結論1:由上面的案例(創建一個定長爲3的線程池,循環執行8個任務)的輸出可以看到:
任務加入到線程池後,線程池即啓動線程執行加入的任務,當啓動線程數達到線程池的上限(這裏爲3)後,剩餘的任務會加入到線程池的等待隊列中,然後主程序繼續向下執行至"方法結束",而等待隊列中的任務會在線程池線程空閒後被依次執行;所有任務結束後,線程池並不會被關閉。

引申改進:
    在開發過程中,我們常常需要等待所有線程任務執行完畢後再返回,這裏我們通過shutdown()isTerminated()兩方法,聯合實現。
代碼:

public void callService() throws InterruptedException {
		//創建一個定長爲3的線程池
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
        
        for (int i = 1; i <= 8; i++){
        	Thread.sleep(100);//稍作等待
        	System.out.println("-------第"+i+"次for循環  -------");
        	
            int sNum = i;
            fixedThreadPool.execute(new Runnable() {//線程池開啓線程任務
                public void run() {
                	SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
            		
            		System.out.println("現在是線程\""+Thread.currentThread().getName()+"\",他在操作第"+sNum+"條數據----開始:"+sdf.format(new Date()));
            		
            		try { 
            			Thread.sleep(5000);//假設,處理一條數據需要5秒
					} catch (InterruptedException e) { e.printStackTrace(); }
            		
        			System.out.println("現在是線程\""+Thread.currentThread().getName()+"\",他在操作第"+sNum+"條數據----結束:"+sdf.format(new Date()));
                }
            });
        }
        
        fixedThreadPool.shutdown();//關閉線程池,不再接收新任務,如果隊列中還有任務,則等待執行完畢
        while (true) {  
            if (fixedThreadPool.isTerminated()) {//關閉後所有任務都已完成,則返回 true
                System.out.println("-------線程池執行完畢 -------");  
                break;
            }  
            Thread.sleep(200);//爲了避免無限制的循環判斷,讓主線程睡眠200毫秒後判斷一次(因此會造成所有任務執行完畢後,200毫秒後纔得到通知,設置時間越小,誤差越小)
        } 
        
        System.out.println("-------方法結束 -------");  
	}


輸出:

 

結論二:額......沒啥了,看註解、看輸出,還不能理解就涼涼了。線程池的一些方法可以看《線程池ExecutorService的主要方法》瞭解下。

 

三、線程的四種用法

剛詳細的講述了一種線程池的用法,想必對線程池有一個較深的印象了,下面再回過頭簡單介紹下"簡述"中提到的4種用法,上代碼看註釋吧:

public class ThreadPoolFourTest {

    /**
     * newSingleThreadExecutor 創建一個單線程化的線程池,
     * 它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行
     */
    public static void threadPool_test_1(){
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        for(int i = 1; i <= 8; i++){
            final int index = i;
            singleThreadExecutor.execute(new Runnable() {
            	
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"執行了第"+index+"個任務");
                }
            });
        }
    }
    
    
    /**
     * newFixedThreadPool 創建一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待
     */
    public static void threadPool_test_2(){
        //當參數爲1的時候,可以控制線程的執行順序
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
        for(int i = 1; i <= 8; i++){
            final int index = i;
            fixedThreadPool.execute(new Runnable() {
            	
                @Override
                public void run() {
                	System.out.println(Thread.currentThread().getName()+"執行第"+index+"個任務");
                	try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace();}
                }
            });
        }
    }
    
    
    /**
     * newCachedThreadPool創建一個可緩存線程池,
     * 如果線程池長度超過處理需要,可靈活回收空閒線程,若無可回收,則新建線程。
     */
    public static void threadPool_test_3(){
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 0; i <= 8; i++) {
        	final int index = i;  
            cachedThreadPool.execute(new Runnable() {
                
                @Override
                public void run() {
                	System.out.println(Thread.currentThread().getName()+"執行第"+index+"個任務");
                }
            });
        }
    }
    
    
	/**
     * 測試newScheduledThreadPool 創建一個定長線程池,支持定時及週期性任務執行。
     * 一般可做定時器使用
     */
    public static void threadPool_test_4(){
    	System.out.println("開始設置newScheduledThreadPool啓動任務啦:"+new Date());
    	
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
        /**
         * 第2個參數:首次執行該線程的延遲時間,之後失效
         * 第3個參數:首次執行完之後,再過該段時間再次執行該線程,具有週期性
         * 第4個參數:時間單位
         */
        scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
            
            @Override
            public void run() {
            	System.out.println("執行了newScheduledThreadPool任務:"+new Date());
                
            }
        }, 2, 3, TimeUnit.SECONDS);
        
    }
	
}


結束:......好了,用法就到這裏了,上面的連接池都是由線程工具類Executors來創建的,newFixedThreadPool、newCachedThreadPool什麼的都是 new ThreadPoolExecutor出來的,只是參數不一樣,還有源碼addWorker()控制線程數啥的,有興趣的可以自己瞭解下,或者關注下,後面整理了發出來。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章