4 線程池應該手動創建還是自動創建
手動創建更好,因為這樣可以讓我們更加了解線程池的運行規則,避免資源耗盡的風險。
4.1 直接調用JDK封裝好的線程池會帶來的問題
newFixedThreadPool
<code>public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<runnable>()); }/<runnable>/<code>
newFixedThreadPool線程池通過傳入相同的corePoolSize和maxPoolSize可以保證線程數量固定,0L的keepAliveTime表示時刻被銷燬,workQueue使用的是無界隊列。
這樣潛在的問題就是當處理任務的速度趕不上任務提交的速度的時候,就可能會讓大量任務堆積在workQueue中,從而引發OOM異常。
4.2 演示newFixedThreadPool內存溢出問題
<code>/** * 演示newFixedThreadPool線程池OOM問題 */public class FixedThreadPoolOOM { private static ExecutorService executorService = Executors.newFixedThreadPool(1); public static void main(String[] args) { for (int i = 0; i < Integer.MAX_VALUE; i++) { executorService.execute(new SubThread()); } }}class SubThread implements Runnable { @Override public void run() { try { //延長任務時間 Thread.sleep(1000000000); } catch (InterruptedException e) { e.printStackTrace(); } }}/<code>
更改JVM參數
![Java線程池的個人總結(二)](http://p2.ttnews.xyz/loading.gif)
運行結果
![Java線程池的個人總結(二)](http://p2.ttnews.xyz/loading.gif)
4.3 newSingleThreadExecutor
<code>public class SingleThreadExecutor { public static void main(String[] args) { ExecutorService executorService = Executors.newSingleThreadExecutor(); for (int i = 0; i < 1000; i++) { executorService.execute(new Task()); } }}/<code>
使用線程池打印線程名
查看newSingleThreadExecutor源碼
<code>public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<runnable>()));}/<runnable>/<code>
從源碼可以看出newSingleThreadExecutor和newFixedThreadPool基本類似,不同的只是corePoolSize和maxPoolSize的值,所以newSingleThreadExecutor也存在內存溢出問題。
4.4 newCachedThreadPool
newCachedThreadPool也被稱為可緩存線程池,它是一個無界線程池,具有自動回收多餘線程的功能。
<code>public class CachedThreadPool { public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < 1000; i++) { executorService.execute(new Task()); } }}/<code>
<code>public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<runnable>());}/<runnable>/<code>
newCachedThreadPool的maxPoolSize設置的值為Integer.MAX_VALUE,所以可能會導致線程被無限創建,最終導致OOM異常。
4.5 newScheduledThreadPool
該線程池支持週期性任務的執行
<code>public class ScheduledThreadPoolTest { public static void main(String[] args) { ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);// scheduledExecutorService.schedule(new Task(), 5, TimeUnit.SECONDS); scheduledExecutorService.scheduleAtFixedRate(new Task(), 1, 3, TimeUnit.SECONDS); }}/<code>
<code>public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue());}/<code>
4.6 正確的創建線程池的方法
根據業務場景不同,自己設置線程池參數,例如內存有多大,自己取線程名字等。
4.7 線程池裡的線程數量設置多少比較合適?
CPU密集型(加密、計算hash等):最佳線程數設置為CPU核心數的1——2倍。
耗時I/O型(讀寫數據庫、文件、網絡讀寫等):最佳線程數一般會大於CPU核心數很多倍,以JVM監控顯示繁忙情況為依據,保證線程空閒可以銜接上。參考Brain Goezt推薦的計算方法:線程數=CPU核心數 × (1+平均等待時間/平均工作時間)
5 對比線程池的特點
FixedThreadPool:通過手動傳入corePoolSize和maxPoolSize,以固定的線程數來執行任務
SingleThreadExecutor:corePoolSize和maxPoolSize默認都是1,全程只以1條線程執行任務
CachedThreadPool:它沒有需要維護的核心線程數,每當需要線程的時候就進行創建,因為它的線程存活時間是60秒,所以它也憑藉著這個參數實現了自動回收的功能。
ScheduledThreadPool:這個線程池可以執行定時任務,corePoolSize是通過手動傳入的,它的maxPoolSize為Integer.MAX_VALUE,並且具有自動回收線程的功能。
5.1 為什麼FixedThreadPool和SingleThreadExecutor的Queue是LinkedBlockingQueue?
因為這兩個線程池的核心線程數和最大線程數都是相同的,也就無法預估任務量,所以需要在自身進行改進,就使用了無界隊列。
5.2 為什麼CachedThreadPool使用的Queue是SynchronousQueue?
因為緩存線程池的最大線程數是“無上限”的,每當任務來的時候直接創建線程進行執行就好了,所以不需要使用隊列來存儲任務。這樣避免了使用隊列進行任務的中轉,提高了執行效率。
5.3 為什麼ScheduledThreadPool使用延遲隊列DelayedWorkQueue?
因為ScheduledThreadPool是延遲任務線程池,所以使用延遲隊列有利於對執行任務的時間做延遲。
5.4 JDK1.8中加入的workStealingPool
- workStealingPool適用於執行產生子任務的環境,例如進行二叉樹的遍歷。
- workStealingPool具有竊取能力。
- 使用時最好不要加鎖,而且不保證執行順序。
閱讀更多 青芽草 的文章