常見的四種線程池
newFixedThreadPool
固定大小的線程池,可以指定線程池的大小,該線程池corePoolSize和maximumPoolSize相等,阻塞隊列使用的是LinkedBlockingQueue,大小為整數最大值。
該線程池中的線程數量始終不變,當有新任務提交時,線程池中有空閑線程則會立即執行,如果沒有,則會暫存到阻塞隊列。對於固定大小的線程池,不存在線程數量的變化。同時使用無界的LinkedBlockingQueue來存放執行的任務。當任務提交十分頻繁的時候,LinkedBlockingQueue
迅速增大,存在着耗盡系統資源的問題。而且在線程池空閑時,即線程池中沒有可運行任務時,它也不會釋放工作線程,還會占用一定的系統資源,需要shutdown。
newSingleThreadExecutor
單個線程線程池,只有一個線程的線程池,阻塞隊列使用的是LinkedBlockingQueue,若有多余的任務提交到線程池中,則會被暫存到阻塞隊列,待空閑時再去執行。按照先入先出的順序執行任務。
newCachedThreadPool
緩存線程池,緩存的線程默認存活60秒。線程的核心池corePoolSize大小為0,核心池最大為Integer.MAX_VALUE,阻塞隊列使用的是SynchronousQueue。是一個直接提交的阻塞隊列, 他總會迫使線程池增加新的線程去執行新的任務。在沒有任務執行時,當線程的空閑時間超過keepAliveTime(60秒),則工作線程將會終止被回收,當提交新任務時,如果沒有空閑線程,則創建新線程執行任務,會導致一定的系統開銷。如果同時又大量任務被提交,而且任務執行的時間不是特別快,那么線程池便會新增出等量的線程池處理任務,這很可能會很快耗盡系統的資源。
newScheduledThreadPool
定時線程池,該線程池可用於周期性地去執行任務,通常用於周期性的同步數據。
scheduleAtFixedRate:是以固定的頻率去執行任務,周期是指每次執行任務成功執行之間的間隔。
schedultWithFixedDelay:是以固定的延時去執行任務,延時是指上一次執行成功之后和下一次開始執行的之前的時間。
使用實例
newFixedThreadPool實例:
public class FixPoolDemo { private static Runnable getThread(final int i) { return new Runnable() { @Override public void run() { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(i); } }; } public static void main(String args[]) { ExecutorService fixPool = Executors.newFixedThreadPool(5); for (int i = 0; i < 10; i++) { fixPool.execute(getThread(i)); } fixPool.shutdown(); } }
newCachedThreadPool實例:
public class CachePool { private static Runnable getThread(final int i){ return new Runnable() { @Override public void run() { try { Thread.sleep(1000); }catch (Exception e){ } System.out.println(i); } }; } public static void main(String args[]){ ExecutorService cachePool = Executors.newCachedThreadPool(); for (int i=1;i<=10;i++){ cachePool.execute(getThread(i)); } } }
這里沒用調用shutDown方法,這里可以發現過60秒之后,會自動釋放資源。
newSingleThreadExecutor
public class SingPoolDemo { private static Runnable getThread(final int i){ return new Runnable() { @Override public void run() { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(i); } }; } public static void main(String args[]) throws InterruptedException { ExecutorService singPool = Executors.newSingleThreadExecutor(); for (int i=0;i<10;i++){ singPool.execute(getThread(i)); } singPool.shutdown(); }
這里需要注意一點,newSingleThreadExecutor和newFixedThreadPool一樣,在線程池中沒有任務時可執行,也不會釋放系統資源的,所以需要shudown。
newScheduledThreadPool
public class ScheduledExecutorServiceDemo { public static void main(String args[]) { ScheduledExecutorService ses = Executors.newScheduledThreadPool(10); ses.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { Thread.sleep(4000); System.out.println(Thread.currentThread().getId() + "執行了"); } catch (InterruptedException e) { e.printStackTrace(); } } }, 0, 2, TimeUnit.SECONDS); } }
如何選擇線程池數量
線程池的大小決定着系統的性能,過大或者過小的線程池數量都無法發揮最優的系統性能。
當然線程池的大小也不需要做的太過於精確,只需要避免過大和過小的情況。一般來說,確定線程池的大小需要考慮CPU的數量,內存大小,任務是計算密集型還是IO密集型等因素
NCPU = CPU的數量
UCPU = 期望對CPU的使用率 0 ≤ UCPU ≤ 1
W/C = 等待時間與計算時間的比率
如果希望處理器達到理想的使用率,那么線程池的最優大小為:
線程池大小=NCPU *UCPU(1+W/C)
在Java中使用
int ncpus = Runtime.getRuntime().availableProcessors();
如何設置參數
- 默認值
-
- corePoolSize=1
- queueCapacity=Integer.MAX_VALUE
- maxPoolSize=Integer.MAX_VALUE
- keepAliveTime=60s
- allowCoreThreadTimeout=false
- rejectedExecutionHandler=AbortPolicy()
-
- 需要根據幾個值來決定
-
- tasks :每秒的任務數,假設為500~1000
- taskcost:每個任務花費時間,假設為0.1s
- responsetime:系統允許容忍的最大響應時間,假設為1s
- 做幾個計算
-
- corePoolSize = 每秒需要多少個線程處理?
-
- threadcount = tasks/(1/taskcost) =tasks*taskcout = (500~1000)*0.1 = 50~100 個線程。corePoolSize設置應該大於50
- 根據8020原則,如果80%的每秒任務數小於800,那么corePoolSize設置為80即可
- queueCapacity = (coreSizePool/taskcost)*responsetime
-
- 計算可得 queueCapacity = 80/0.1*1 = 80。意思是隊列里的線程可以等待1s,超過了的需要新開線程來執行
- 切記不能設置為Integer.MAX_VALUE,這樣隊列會很大,線程數只會保持在corePoolSize大小,當任務陡增時,不能新開線程來執行,響應時間會隨之陡增。
- maxPoolSize = (max(tasks)- queueCapacity)/(1/taskcost)
-
- 計算可得 maxPoolSize = (1000-80)/10 = 92
- (最大任務數-隊列容量)/每個線程每秒處理能力 = 最大線程數
- rejectedExecutionHandler:根據具體情況來決定,任務不重要可丟棄,任務重要則要利用一些緩沖機制來處理
- keepAliveTime和allowCoreThreadTimeout采用默認通常能滿足