池的概念在java中也是常見,還有鏈接池、常量池等,池的做用也是相似的,對於對象、資源的重複利用,減少系統開銷,提高運行效率。java
線程池的主要功能:
1.減小建立和銷燬線程的次數,提高運行性能,尤爲是在大量異步任務時
2.能夠更合理地管理線程,如:線程的運行數量,防止同一時間大量任務運行,致使系統崩潰多線程
先舉個demo,看看使用線程池的區別,線程池:異步
AtomicLong al = new AtomicLong(0l); Long s1 = System.currentTimeMillis(); ThreadPoolExecutor pool = new ThreadPoolExecutor(5, 100000, 100, TimeUnit.SECONDS, new LinkedBlockingDeque(20000)); for(int i=0;i<20000;i++){ pool.execute(new Runnable() { @Override public void run() { try { al.incrementAndGet(); } catch (Exception e) { e.printStackTrace(); } } }); } pool.shutdown(); pool.awaitTermination(1, TimeUnit.HOURS); System.out.println("耗時:"+(System.currentTimeMillis()-s1)); System.out.println("AtomicLong="+al.get());
結果:ide
耗時:224 AtomicLong=20000
非線程池:性能
AtomicLong al = new AtomicLong(0l); Long s1 = System.currentTimeMillis(); for(int i=0;i<20000;i++){ Thread t = new Thread(new Runnable() { @Override public void run() { try { al.incrementAndGet(); } catch (Exception e) { e.printStackTrace(); } } }); t.start(); } while(true){ if(al.get()==20000){ System.out.println("耗時:"+(System.currentTimeMillis()-s1)); System.out.println("AtomicLong="+al.get()); break; } Thread.sleep(1); }
結果:測試
耗時:2972 AtomicLong=20000
從耗時看2者相差了13倍,差距仍是挺大的,可見有大量異步任務時使用線程池可以提高性能。線程
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
線程池的主要參數有這6個:code
corePoolSize:核心池的大小,核心池的線程不會被回收,沒有任務就處於空閒狀態。對象
maximumPoolSize:線程池最大容許的線程數,接口
keepAliveTime:當線程數超過corePoolSize時,但小於等於maximumPoolSize,會生成‘臨時’線程,‘臨時’線程執行完任務不會立刻被回收,若是在keepAliveTime時間內,尚未新任務的話,纔會被回收。
unit:keepAliveTime的單位。
workQueue:當前線程數超過corePoolSize大小時,新任務會處在等待狀態,存在workQueue中。
threadFactory:生成新線程的工廠。
handler:當線程數超過workQueue的上限時,新線程會被拒絕,這個參數就是新線程被拒絕的方式。
其中corePoolSize和maximumPoolSize相對難理解,再詳細說明:
一、池中線程數小於corePoolSize,新任務都不排隊而是直接添加新線程
二、池中線程數大於等於corePoolSize,workQueue未滿,首選將新任務加入workQueue而不是添加新線程
三、池中線程數大於等於corePoolSize,workQueue已滿,可是線程數小於maximumPoolSize,添加新的線程來處理被添加的任務
四、池中線程數大於大於corePoolSize,workQueue已滿,而且線程數大於等於maximumPoolSize,新任務被拒絕,使用handler處理被拒絕的任務
所以maximumPoolSize、corePoolSize不宜設置過大,不然會形成內存、cpu過載的問題,workQueue而儘可能能夠大一些。
雖然ThreadPoolExecutor使用很方便,可是建議你們使用jdk已經設定的幾種線程池:
Executors.newCachedThreadPool()(無界線程池,能夠進行線程自動回收)、Executors.newFixedThreadPool(int)(固定大小線程池)和Executors.newSingleThreadExecutor()(單個後線程),它們知足大部分的場景需求。
1.newSingleThreadExecutor 單線程線程池
看下它的實現方式:
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
把corePoolSize設爲1,而workQueue大小設爲無限大,所以永遠只有一個線程在執行任務,新任務都在workQueue中等待。
2.newFixedThreadPool 固定大小線程池
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
和newSingleThreadExecutor 有些相似,只不過從單線程變成能夠指定線程數量,workQueue依舊爲無限。
3.newCachedThreadPool
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
newCachedThreadPool全部新任務都會被當即執行,corePoolSize 設置爲0,maximumPoolSize設置爲整數最大值,全部線程超過60秒未被使用,就會被銷燬。
當前線程數超過corePoolSize大小時,新任務會處在等待狀態,存在workQueue中。workQueue的實現方式也就是任務的等待策略,分爲3種:有限隊列、無限隊列、直接提交。
談談前2種,jdk使用了LinkedBlockingQueue而非ArrayBlockingQueue,有限隊列的優點在於對資源和效率更定製化的配置,可是對比無限隊列缺點也很明顯:
1).增長開發難度。須要考慮到corePoolSize ,maximumPoolSize,workQueue,3個參數大小,既要使任務高效地執行,又要避免任務超量的問題,對於開發和測試的複雜度提升不少。
2).防止業務突刺。在互聯網應用業務量暴增也是必須考慮的問題,在使用無限隊列時,雖然執行可能慢一點,可是能保證執行到。使用有限隊列,會出現任務拒絕的問題。
綜上考慮,更建議使用無限隊列,尤爲是對於多線程不是很熟悉的朋友們。
所謂拒絕策略以前也提到過了,任務太多,超過maximumPoolSize了怎麼辦?固然是接不下了,接不下那只有拒絕了。拒絕的時候能夠指定拒絕策略,也就是一段處理程序。
決絕策略的父接口是RejectedExecutionHandler,JDK自己在ThreadPoolExecutor裏給用戶提供了四種拒絕策略,看一下:
一、AbortPolicy
直接拋出一個RejectedExecutionException,這也是JDK默認的拒絕策略
二、CallerRunsPolicy
嘗試直接運行被拒絕的任務,若是線程池已經被關閉了,任務就被丟棄。
三、DiscardOldestPolicy
移除最晚的那個沒有被處理的任務,而後執行被拒絕的任務。一樣,若是線程池已經被關閉了,任務就被丟棄了
四、DiscardPolicy
悄悄地拒絕任務