ThreadPoolExecutor類詳解
JDK的ThreadPoolExecutor類實現了執行緒池,在它的建構函式中,有五個引數,讓我們看看這五個引數具體作用是什麼。
- corePoolSize,核心執行緒池數量。
預設情況下會一直存活,當請求進來時,如果當時的存活執行緒數小於核心執行緒數量,就會去新建立執行緒來處理這個請求,即使有其他執行緒是空閒的。在allowCoreThreadTimeout引數設定會true時,核心執行緒也會超時退出,預設是false超時不退出。
- maxPoolSize,存活執行緒最大數量。
當執行緒數大於或等於核心執行緒,且任務佇列已滿時,執行緒池會繼續建立新的執行緒處理請求,直到maxPoolSize的限制數量。如果當前存活的執行緒已經到了maxPoolSize的限制數量,且任務佇列已滿,執行緒池會拒絕處理任務並丟擲異常。
- keepAliveTime,執行緒可空閒時間。
當執行緒空閒時間打到keepAliveTime時,執行緒會退出,直到存活執行緒數量等於核心執行緒數量。如果allowCoreThreadTimeout引數設定會true時,核心執行緒也會退出,直到執行緒數為0。
- TimeUnit,空閒執行緒的保留時間單位。
- workQueue ,快取工作佇列,用來快取等待執行的任務。
- queueCapacity,任務佇列容量。
- handler:表示當拒絕處理任務時的策略。
有四種取值:
(1)ThreadPoolExecutor.AbortPolicy:丟棄任務並丟擲RejectedExecutionException異常,預設此配置。
(2)ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不丟擲異常。
(3)ThreadPoolExecutor.DiscardOldestPolicy:丟棄佇列最前面的任務,然後重新嘗試執行任務(重複此過程)。
(4)ThreadPoolExecutor.CallerRunsPolicy:由呼叫執行緒處理該任務。
任務佇列的選用
任務佇列一般選用LinkedBlockingQueue和Synchronous。注意當使用LinkedBlockingQueue時,一定要設定佇列的大小,否則會預設設定佇列的大小為Integer.MaxValue,可能會造成資源耗盡。
執行緒池的關閉
ThreadPoolExecutor提供了兩個方法去關閉執行緒池。
shutdown():不會立即終止執行緒池,而是要等所有任務快取佇列中的任務都執行完後才終止,但再也不會接受新的任務。
shutdownNow():立即終止執行緒池,並嘗試打斷正在執行的任務,並且清空任務快取佇列,返回尚未執行的任務。
程式碼驗證
我們來寫一個測試類驗證一下
public static void main(String[] args) {
int corePoolSize = 2;
int maximumPoolSize = 5;
int keepAliveTime = 10;
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(2);
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime,
TimeUnit.SECONDS, workQueue);
try {
test(threadPoolExecutor, 3);
} catch (Exception e) {
System.out.println(e.getMessage());
} finally {
//最後關閉執行緒池
threadPoolExecutor.shutdown();
}
}
public static void test(ThreadPoolExecutor threadPoolExecutor, int taskCount) {
for (int i = 0; i < taskCount; i++) {
threadPoolExecutor.submit(new TaskTest());
System.out.println("總任務數:" + threadPoolExecutor.getTaskCount() + ",已完成任務數:" + threadPoolExecutor.getCompletedTaskCount()
+ ",執行緒池執行緒數目:" + threadPoolExecutor.getPoolSize() + ",處於執行狀態的數目:" + threadPoolExecutor.getActiveCount());
System.out.println("快取佇列任務數:"+threadPoolExecutor.getQueue().size());
}
}
static class TaskTest implements Runnable {
@Override
public void run() {
try {
//為了更好的效果,休眠一秒
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "執行完畢");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
我們先把核心執行緒數設定成2,最大執行緒數設定成5,快取工作佇列設定成2,然後把我們要執行的任務書設定成3個,看下執行的結果是什麼。
總任務數:1,已完成任務數:0,執行緒池執行緒數目:1,處於執行狀態的數目:1
快取佇列任務數:0
總任務數:2,已完成任務數:0,執行緒池執行緒數目:2,處於執行狀態的數目:2
快取佇列任務數:0
總任務數:3,已完成任務數:0,執行緒池執行緒數目:2,處於執行狀態的數目:2
快取佇列任務數:1
pool-1-thread-2執行完畢
pool-1-thread-1執行完畢
pool-1-thread-2執行完畢
從最後執行完畢時列印的執行緒名稱可知,最多隻有兩個執行緒在執行任務(核心執行緒數為2),而當前的總任務數達到核心任務數後,新的任務會被放到快取佇列中,因為快取佇列還沒滿,所以執行緒池不會去建立新的執行緒去處理任務。那我們再來試一下快取佇列滿了之後會怎麼樣,我們把任務數設定成5,結果如下。
總任務數:1,已完成任務數:0,執行緒池執行緒數目:1,處於執行狀態的數目:1
快取佇列任務數:0
總任務數:2,已完成任務數:0,執行緒池執行緒數目:2,處於執行狀態的數目:2
快取佇列任務數:0
總任務數:3,已完成任務數:0,執行緒池執行緒數目:2,處於執行狀態的數目:2
快取佇列任務數:1
總任務數:4,已完成任務數:0,執行緒池執行緒數目:2,處於執行狀態的數目:2
快取佇列任務數:2
總任務數:5,已完成任務數:0,執行緒池執行緒數目:3,處於執行狀態的數目:2
快取佇列任務數:2
pool-1-thread-1執行完畢
pool-1-thread-2執行完畢
pool-1-thread-3執行完畢
pool-1-thread-2執行完畢
pool-1-thread-1執行完畢
果然,第三個執行緒出來了,當快取佇列也滿了的時候,執行緒池會建立新的執行緒去處理任務,當然這個執行緒數也是會被我們設定的maximumPoolSize最大執行緒數限制的,最大允許執行緒數為5個,再加上快取佇列的兩個,所以最大可處理為7個任務,當第八個任務來了的時候會怎麼樣呢,我們來試試,把任務數提高到8個,結果如下。
總任務數:1,已完成任務數:0,執行緒池執行緒數目:1,處於執行狀態的數目:1
快取佇列任務數:0
總任務數:2,已完成任務數:0,執行緒池執行緒數目:2,處於執行狀態的數目:2
快取佇列任務數:0
總任務數:3,已完成任務數:0,執行緒池執行緒數目:2,處於執行狀態的數目:2
快取佇列任務數:1
總任務數:4,已完成任務數:0,執行緒池執行緒數目:2,處於執行狀態的數目:2
快取佇列任務數:2
總任務數:5,已完成任務數:0,執行緒池執行緒數目:3,處於執行狀態的數目:3
快取佇列任務數:2
總任務數:6,已完成任務數:0,執行緒池執行緒數目:4,處於執行狀態的數目:4
快取佇列任務數:2
總任務數:7,已完成任務數:0,執行緒池執行緒數目:5,處於執行狀態的數目:5
快取佇列任務數:2
Task java.util.concurrent.FutureTask@68de145 rejected from java.util.concurrent.ThreadPoolExecutor@27fa135a[Running, pool size = 5, active threads = 5, queued tasks = 2, completed tasks = 0]
pool-1-thread-1執行完畢
pool-1-thread-3執行完畢
pool-1-thread-5執行完畢
pool-1-thread-2執行完畢
pool-1-thread-4執行完畢
pool-1-thread-1執行完畢
pool-1-thread-3執行完畢
最終被處理的任務數為七個,而且丟擲了異常,第八個任務被捨棄掉了(預設ThreadPoolExecutor.AbortPolicy:丟棄任務並丟擲RejectedExecutionException異常)。
我們再來測試一下兩種關閉執行緒池方法的區別,先來試試shoudownNow()方法,在原來的程式碼上改動一下。
test(threadPoolExecutor, 4);
List<Runnable> runnableList = threadPoolExecutor.shutdownNow();
System.out.println("未處理任務數:"+runnableList.size());
結果如下:
總任務數:1,已完成任務數:0,執行緒池執行緒數目:1,處於執行狀態的數目:1
快取佇列任務數:0
總任務數:2,已完成任務數:0,執行緒池執行緒數目:2,處於執行狀態的數目:2
快取佇列任務數:0
總任務數:3,已完成任務數:0,執行緒池執行緒數目:2,處於執行狀態的數目:2
快取佇列任務數:1
總任務數:4,已完成任務數:0,執行緒池執行緒數目:2,處於執行狀態的數目:2
快取佇列任務數:2
未處理任務數:2
pool-1-thread-2執行完畢
pool-1-thread-1執行完畢
關閉執行緒池後返回的未處理任務數為2,最終也只處理了前兩條任務。我們再來試試shutdown()方法。
threadPoolExecutor.shutdown();
結果如下:
總任務數:1,已完成任務數:0,執行緒池執行緒數目:1,處於執行狀態的數目:1
快取佇列任務數:0
總任務數:2,已完成任務數:0,執行緒池執行緒數目:2,處於執行狀態的數目:2
快取佇列任務數:0
總任務數:3,已完成任務數:0,執行緒池執行緒數目:2,處於執行狀態的數目:2
快取佇列任務數:1
總任務數:4,已完成任務數:0,執行緒池執行緒數目:2,處於執行狀態的數目:2
快取佇列任務數:2
pool-1-thread-2執行完畢
pool-1-thread-2執行完畢
pool-1-thread-2執行完畢
pool-1-thread-1執行完畢
四個任務全部執行完成。
今天的總結就到這裡,共勉。