Java自帶的線程池ThreadPoolExecutor詳細介紹說明和實例應用 – JAVA編程語言程序開發技術文章

 Java 5 開始,Java 提供瞭自己的線程池。線程池就是一個線程的容器,每次隻執行額定數量的線程。 java.util.concurrent.ThreadPoolExecutor 就是這樣的線程池。它很靈活,但使用起來也比較復雜,本文就對其做一個介紹。
 
首先是構造函數。以最簡單的構造函數為例:
[java] 
public ThreadPoolExecutor(    
            int corePoolSize,    
            int maximumPoolSize,    
            long keepAliveTime,    
            TimeUnit unit,    
            BlockingQueue workQueue)   
    public ThreadPoolExecutor( int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue) 
看起來挺復雜的。這裡介紹一下。

corePoolSize 指的是保留的線程池大小。
maximumPoolSize 指的是線程池的最大大小。
keepAliveTime 指的是空閑線程結束的超時時間。
unit 是一個枚舉,表示 keepAliveTime 的單位。
workQueue 表示存放任務的隊列。
我們可以從線程池的工作過程中瞭解這些參數的意義。線程池的工作過程如下:
 
1、線程池剛創建時,裡面沒有一個線程。任務隊列是作為參數傳進來的。不過,就算隊列裡面有任務,線程池也不會馬上執行它們。
2、當調用 execute() 方法添加一個任務時,線程池會做如下判斷:
    a. 如果正在運行的線程數量小於 corePoolSize,那麼馬上創建線程運行這個任務;
    b. 如果正在運行的線程數量大於或等於 corePoolSize,那麼將這個任務放入隊列。
    c. 如果這時候隊列滿瞭,而且正在運行的線程數量小於 maximumPoolSize,那麼還是要創建線程運行這個任務;
    d. 如果隊列滿瞭,而且正在運行的線程數量大於或等於 maximumPoolSize,那麼線程池會拋出異常,告訴調用者“我不能再接受任務瞭”。
3、當一個線程完成任務時,它會從隊列中取下一個任務來執行。
4、當一個線程無事可做,超過一定的時間(keepAliveTime)時,線程池會判斷,如果當前運行的線程數大於 corePoolSize,那麼這個線程就被停掉。所以線程池的所有任務完成後,它最終會收縮到 corePoolSize 的大小。
這樣的過程說明,並不是先加入任務就一定會先執行。假設隊列大小為 10,corePoolSize 為 3,maximumPoolSize 為 6,那麼當加入 20 個任務時,執行的順序就是這樣的:首先執行任務 1、2、3,然後任務 4~13 被放入隊列。這時候隊列滿瞭,任務 14、15、16 會被馬上執行,而任務 17~20 則會拋出異常。最終順序是:1、2、3、14、15、16、4、5、6、7、8、9、10、11、12、13。下面是一個線程池使用的例子:

[java]
public static void main(String[] args) {    
    BlockingQueue queue = new LinkedBlockingQueue();    
    ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 6, 1, TimeUnit.DAYS, queue);    
     
    for (int i = 0; i < 20; i++) {    
        executor.execute(new Runnable() {    
     
            public void run() {    
                try {    
                    Thread.sleep(1000);    
                } catch (InterruptedException e) {    
                    e.printStackTrace();    
                }    
                System.out.println(String.format("thread %d finished", this.hashCode()));    
            }    
        });    
    }    
    executor.shutdown();    

對這個例子的說明如下:
1、BlockingQueue 隻是一個接口,常用的實現類有 LinkedBlockingQueue 和 ArrayBlockingQueue。用 LinkedBlockingQueue 的好處在於沒有大小限制。這樣的話,因為隊列不會滿,所以 execute() 不會拋出異常,而線程池中運行的線程數也永遠不會超過 corePoolSize 個,keepAliveTime 參數也就沒有意義瞭。
2、shutdown() 方法不會阻塞。調用 shutdown() 方法之後,主線程就馬上結束瞭,而線程池會繼續運行直到所有任務執行完才會停止。如果不調用 shutdown() 方法,那麼線程池會一直保持下去,以便隨時添加新的任務。
到這裡對於這個線程池還隻是介紹瞭一小部分。ThreadPoolExecutor 具有很強的可擴展性,不過擴展它的前提是要熟悉它的工作方式。
 
java.util.concurrent.ThreadPoolExecutor 類提供瞭豐富的可擴展性。你可以通過創建它的子類來自定義它的行為。例如,我希望當每個任務結束之後打印一條消息,但我又無法修改任務對象,那麼我可以這樣寫:
 
[java] 
ThreadPoolExecutor executor = new ThreadPoolExecutor(size, maxSize, 1, TimeUnit.DAYS, queue) { 
    @Override 
    protected void afterExecute(Runnable r, Throwable t) { 
        System.out.println("Task finished."); 
    } 
};<span style="font-family: Arial; text-indent: 2em;"> </span> 
除瞭 afterExecute 方法之外,ThreadPoolExecutor 類還有 beforeExecute() 和 terminated() 方法可以重寫,分別是在任務執行之前和整個線程池停止之後執行。
 
 
除瞭可以添加任務執行前後的動作之外, ThreadPoolExecutor 還允許你自定義當添加任務失敗後的執行策略。你可以調用線程池的 setRejectedExecutionHandler() 方法,用自定義的 RejectedExecutionHandler 對象替換現有的策略。 ThreadPoolExecutor 提供 4 個現有的策略,分別是:

ThreadPoolExecutor.AbortPolicy:表示拒絕任務並拋出異常
ThreadPoolExecutor.DiscardPolicy:表示拒絕任務但不做任何動作
ThreadPoolExecutor.CallerRunsPolicy:表示拒絕任務,並在調用者的線程中直接執行該任務
ThreadPoolExecutor.DiscardOldestPolicy:表示先丟棄任務隊列中的第一個任務,然後把這個任務加進隊列。
這裡是一個例子:
[java]
ThreadPoolExecutor executor = new ThreadPoolExecutor(size, maxSize, 1, TimeUnit.DAYS, queue); 
    executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy()); 
除此之外,你也可以通過實現 RejectedExecutionHandler 接口來編寫自己的策略。下面是一個例子:
 
[java]
ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 6, 1, TimeUnit.SECONDS, queue, 
        new RejectedExecutionHandler() { 
            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { 
                System.out.println(String.format("Task %d rejected.", r.hashCode())); 
            } 
        } 
); 

發佈留言