1. 程式人生 > >Java中ThreadPoolExecutor的詳細介紹

Java中ThreadPoolExecutor的詳細介紹

從 Java 5 開始,Java 提供了自己的執行緒池。執行緒池就是一個執行緒的容器,每次只執行額定數量的執行緒。 java.util.concurrent.ThreadPoolExecutor 就是這樣的執行緒池。它很靈活,但使用起來也比較複雜,本文就對其做一個介紹。

首先是建構函式。以最簡單的建構函式為例:

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。下面是一個執行緒池使用的例子:

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 類提供了豐富的可擴充套件性。你可以通過建立它的子類來自定義它的行為。例如,我希望當每個任務結束之後列印一條訊息,但我又無法修改任務物件,那麼我可以這樣寫:

ThreadPoolExecutor executor = new ThreadPoolExecutor(size, maxSize, 1, TimeUnit.DAYS, queue) { 
    @Override 
    protected void afterExecute(Runnable r, Throwable t) { 
        System.out.println("Task finished."); 
    } 
};

除了 afterExecute 方法之外,ThreadPoolExecutor 類還有 beforeExecute() 和 terminated() 方法可以重寫,分別是在任務執行之前和整個執行緒池停止之後執行。

除了可以新增任務執行前後的動作之外, ThreadPoolExecutor 還允許你自定義當新增任務失敗後的執行策略。你可以呼叫執行緒池的 setRejectedExecutionHandler() 方法,用自定義的 RejectedExecutionHandler 物件替換現有的策略。 ThreadPoolExecutor 提供 4 個現有的策略,分別是:

ThreadPoolExecutor.AbortPolicy:表示拒絕任務並丟擲異常
ThreadPoolExecutor.DiscardPolicy:表示拒絕任務但不做任何動作
ThreadPoolExecutor.CallerRunsPolicy:表示拒絕任務,並在呼叫者的執行緒中直接執行該任務
ThreadPoolExecutor.DiscardOldestPolicy:表示先丟棄任務佇列中的第一個任務,然後把這個任務加進佇列。

這裡是一個例子:

ThreadPoolExecutor executor = new ThreadPoolExecutor(size, maxSize, 1, TimeUnit.DAYS, queue);

executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());

除此之外,你也可以通過實現 RejectedExecutionHandler 介面來編寫自己的策略。下面是一個例子:

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()));
            }
        }

);

【部落格】推薦: