JDK中執行緒池參詳細解析
在jdk中為我們提供了三種建立執行緒池的方式,但是在阿里的編碼規範裡面都是明確禁止使用這三種api去建立執行緒池,推薦我們去自定義執行緒池。為什麼?
要回答為什麼,我們需要明白建立執行緒池時,各引數的作用:
首先我們來看一下jdk提供的建立執行緒池的三個api:
1. newFixedThreadPool 建立固定數量執行緒的執行緒池。
2. newSingleThreadExecutor 建立單執行緒的執行緒池
3. newCachedThreadPool 建立一個帶有快取的執行緒池
發現這幾種建立執行緒池的api,實質上都是依賴於ThreadPoolExecutor類來建立執行緒池。
那我們來看一下ThreadPoolExecutor 建立執行緒池時需要的引數,以及其作用。
建立一個執行緒池,需要7個引數。
1. corePoolSize: 執行緒池的核心執行緒數量。初始是不建立執行緒的。當有任務提交到執行緒池時,判定如果已經建立的執行緒數量小於核心數量,且沒有空閒執行緒時,則會新建一個執行緒去執行新提交的任務。如果已經達到核心執行緒數量, 則會加入到阻塞佇列中。
2.maximumPoolSize: 執行緒池的最大容量。當執行緒池的阻塞佇列放滿了, 並且執行緒數量還未達到執行緒池的最大執行緒數量, 則會建立新的執行緒,直到達到最大值
3.keepAliveTime 當阻塞佇列裡面的任務被執行完了, 且有空閒執行緒時,指定大於核心執行緒池數量的部分空閒執行緒的存活時間, 畢竟執行緒也是需要消耗資源的,及時回收很有必要。當執行緒空閒的時間超過這個時間後,會回收掉一部分空閒執行緒,使其執行緒池中的執行緒數量不大於核心執行緒的數量
4.unit 和keepAliveTIme 配套使用,上面指定了時間的數值,但是沒有指定時間的單位(時,分,秒等), 這裡需要指定時間的單位
5.workQueue 阻塞佇列,當沒有空閒執行緒時,多餘的任務快取的地方。
6.threadFactory 執行緒工廠,用來建立執行緒時,設定執行緒的一些引數。通常我們為了後續檢視日誌方便,可以通過這個來指定我們自定義的執行緒池的執行緒名稱
7.handler 當執行緒數量達到最大值時,且阻塞佇列慢了, 後續在提交任務時,沒有地方可以接受繼續的提交的任務。這種情況下的一個拒絕策略。
拒絕策略jdK,提供了四種:
// 由提交任務的執行緒執行任務
public static class CallerRunsPolicy implements RejectedExecutionHandler { public CallerRunsPolicy() { } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { r.run(); } } }
// 不在接收新的任務,直接丟擲異常 public static class AbortPolicy implements RejectedExecutionHandler { public AbortPolicy() { } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString()); } }
// 不接收也不丟擲異常,空實現,忽略該任務 public static class DiscardPolicy implements RejectedExecutionHandler { public DiscardPolicy() { } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { } } // 拋棄最老的任務,從阻塞佇列中移除最早提交的任務,然後將該任務加入。 public static class DiscardOldestPolicy implements RejectedExecutionHandler { public DiscardOldestPolicy() { } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { e.getQueue().poll(); e.execute(r); } } }
講解完了建立線池時,各引數的作用,那麼我們現在再反過來看為什麼不讓使用jdk提供的apI來建立執行緒池,而是需要我們自定義執行緒池。
newFixedThreadPool ,newSingleThreadExecutor 這兩種api 使用的阻塞佇列都是無界佇列,也就是無論有多少個任務來,我們都接收。我們的記憶體是有限的,阻塞佇列裡面儲存的任務是越多,也就意味著佔用的記憶體越多,這樣會導致佔用大量的記憶體,容易引起OOM
newCachedThreadPool 而這個api 的阻塞佇列容量為0,最大執行緒數量為Integer 的最大值。每當有一個任務提交時,阻塞佇列儲存不了,就會新開啟一個執行緒,當任務比較多,則會建立大量的執行緒, 引起OOM.
這就是為什麼我們在使用執行緒池時一定要自定義執行緒池的原因了。
&n