Java創(chuàng)建線程池為什么一定要用ThreadPoolExecutor
前言:
在 Java 語(yǔ)言中,并發(fā)編程都是依靠線程池完成的,而線程池的創(chuàng)建方式又有很多,但從大的分類來(lái)說(shuō),線程池的創(chuàng)建總共分為兩大類:手動(dòng)方式使用ThreadPoolExecutor
創(chuàng)建線程池和使用 Executors 執(zhí)行器自動(dòng)創(chuàng)建線程池。 那究竟要使用哪種方式來(lái)創(chuàng)建線程池呢?我們今天就來(lái)詳細(xì)的聊一聊。
先說(shuō)結(jié)論
在 Java 語(yǔ)言中,一定要使用 ThreadPoolExecutor 手動(dòng)的方式來(lái)創(chuàng)建線程池,因?yàn)檫@種方式可以通過(guò)參數(shù)來(lái)控制最大任務(wù)數(shù)和拒絕策略,讓線程池的執(zhí)行更加透明和可控,并且可以規(guī)避資源耗盡的風(fēng)險(xiǎn)。
OOM風(fēng)險(xiǎn)演示
假如我們使用了 Executors 執(zhí)行器自動(dòng)創(chuàng)建線程池的方式來(lái)創(chuàng)建線程池,那么就會(huì)存現(xiàn)線程溢出的風(fēng)險(xiǎn),
以 CachedThreadPool 為例我們來(lái)演示一下:
import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPoolExecutorExample { static class OOMClass { // 創(chuàng)建 1MB 大小的變量(1M = 1024KB = 1024*1024Byte) private byte[] data_byte = new byte[1 * 1024 * 1024]; } public static void main(String[] args) throws InterruptedException { // 使用執(zhí)行器自動(dòng)創(chuàng)建線程池 ExecutorService threadPool = Executors.newCachedThreadPool(); List<Object> list = new ArrayList<>(); // 添加任務(wù) for (int i = 0; i < 10; i++) { int finalI = i; threadPool.execute(new Runnable() { @Override public void run() { // 定時(shí)添加 try { Thread.sleep(finalI * 200); } catch (InterruptedException e) { e.printStackTrace(); } // 將 1M 對(duì)象添加到集合 OOMClass oomClass = new OOMClass(); list.add(oomClass); System.out.println("執(zhí)行任務(wù):" + finalI); } }); } } }
第 2 步將 Idea 中 JVM 最大運(yùn)行內(nèi)存設(shè)置為 10M(設(shè)置此值主要是為了方便演示),如下圖所示:
以上程序的執(zhí)行結(jié)果如下圖所示:
從上述結(jié)果可以看出,當(dāng)線程執(zhí)行了 7 次之后就開始出現(xiàn)OutOfMemoryError
內(nèi)存溢出的異常了。
內(nèi)存溢出原因分析
想要了解內(nèi)存溢出的原因,我們需要查看 CachedThreadPool 實(shí)現(xiàn)的細(xì)節(jié),它的源碼如下圖所示:
構(gòu)造函數(shù)的第 2 個(gè)參數(shù)被設(shè)置成了 Integer.MAX_VALUE,這個(gè)參數(shù)的含義是最大線程數(shù),所以由于 CachedThreadPool 并不限制線程的數(shù)量,當(dāng)任務(wù)數(shù)量特別多的時(shí)候,就會(huì)創(chuàng)建非常多的線程。而上面的 OOM 示例,每個(gè)線程至少要消耗 1M 大小的內(nèi)存,加上 JDK 系統(tǒng)類的加載也要占用一部分的內(nèi)存,所以當(dāng)總的運(yùn)行內(nèi)存大于 10M 的時(shí)候,就出現(xiàn)內(nèi)存溢出的問(wèn)題了。
使用ThreadPoolExecutor來(lái)改進(jìn)
接下來(lái)我們使用 ThreadPoolExecutor 來(lái)改進(jìn)一下 OOM 的問(wèn)題,我們使用 ThreadPoolExecutor 手動(dòng)創(chuàng)建線程池的方式,創(chuàng)建一個(gè)最大線程數(shù)為 2,最多可存儲(chǔ) 2 個(gè)任務(wù)的線程池,并且設(shè)置線程池的拒絕策略為忽略新任務(wù),這樣就能保證線程池的運(yùn)行內(nèi)存大小不會(huì)超過(guò) 10M 了,
實(shí)現(xiàn)代碼如下:
import java.util.ArrayList; import java.util.List; import java.util.concurrent.*; /** * ThreadPoolExecutor 演示示例 */ public class ThreadPoolExecutorExample { static class OOMClass { // 創(chuàng)建 1MB 大小的變量(1M = 1024KB = 1024*1024Byte) private byte[] data_byte = new byte[1 * 1024 * 1024]; } public static void main(String[] args) throws InterruptedException { // 手動(dòng)創(chuàng)建線程池,最大線程數(shù) 2,最多存儲(chǔ) 2 個(gè)任務(wù),其他任務(wù)會(huì)被忽略 ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 2, 0L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(2), new ThreadPoolExecutor.DiscardPolicy()); // 拒絕策略:忽略任務(wù) List<Object> list = new ArrayList<>(); // 添加任務(wù) for (int i = 0; i < 10; i++) { int finalI = i; threadPool.execute(new Runnable() { @Override public void run() { // 定時(shí)添加 try { Thread.sleep(finalI * 200); } catch (InterruptedException e) { e.printStackTrace(); } // 將 1m 對(duì)象添加到集合 OOMClass oomClass = new OOMClass(); list.add(oomClass); System.out.println("執(zhí)行任務(wù):" + finalI); } }); } // 關(guān)閉線程池 threadPool.shutdown(); // 檢測(cè)線程池的任務(wù)執(zhí)行完 while (!threadPool.awaitTermination(3, TimeUnit.SECONDS)) { System.out.println("線程池中還有任務(wù)在處理"); } } }
以上程序的執(zhí)行結(jié)果如下圖所示:
從上述結(jié)果可以看出,線程池從開始執(zhí)行到執(zhí)行結(jié)束都沒(méi)有出現(xiàn) OOM 的異常,這就是手動(dòng)創(chuàng)建線程池的優(yōu)勢(shì)。
其他創(chuàng)建線程池的問(wèn)題
除了 CachedThreadPool 線程池之外,其他使用 Executors 自動(dòng)創(chuàng)建線程池的方式,也存在著其他一些問(wèn)題,
比如 FixedThreadPool 它的實(shí)現(xiàn)源碼如下:
而默認(rèn)情況下任務(wù)隊(duì)列LinkedBlockingQueue
的存儲(chǔ)容量是 Integer.MAX_VALUE,也是趨向于無(wú)限大
如下圖所示:
這樣就也會(huì)造成,因?yàn)榫€程池的任務(wù)過(guò)多而導(dǎo)致的內(nèi)存溢出問(wèn)題。其他幾個(gè)使用 Executors 自動(dòng)創(chuàng)建線程池的方式也存在此問(wèn)題,這里就不一一演示了。
總結(jié)
線程池的創(chuàng)建方式總共分為兩大類:手動(dòng)使用 ThreadPoolExecutor 創(chuàng)建線程池和自動(dòng)使用 Executors 執(zhí)行器創(chuàng)建線程池的方式。其中使用 Executors 自動(dòng)創(chuàng)建線程的方式,因?yàn)榫€程個(gè)數(shù)或者任務(wù)個(gè)數(shù)不可控,可能會(huì)導(dǎo)致內(nèi)存溢出的風(fēng)險(xiǎn),所以在創(chuàng)建線程池時(shí),建議使用 ThreadPoolExecutor 的方式來(lái)創(chuàng)建。
到此這篇關(guān)于Java創(chuàng)建線程池為什么一定要用ThreadPoolExecutor的文章就介紹到這了,更多相關(guān)Java線程池創(chuàng)建內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring Boot集成MyBatis實(shí)現(xiàn)通用Mapper的配置及使用
關(guān)于MyBatis,大部分人都很熟悉。MyBatis 是一款優(yōu)秀的持久層框架,它支持定制化 SQL、存儲(chǔ)過(guò)程以及高級(jí)映射。這篇文章主要介紹了Spring Boot集成MyBatis實(shí)現(xiàn)通用Mapper,需要的朋友可以參考下2018-08-08Spring學(xué)習(xí)通過(guò)AspectJ注解方式實(shí)現(xiàn)AOP操作
這篇文章主要為大家介紹了Spring學(xué)習(xí)通過(guò)AspectJ注解方式實(shí)現(xiàn)AOP操作,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05java?String到底有多長(zhǎng)?String超出長(zhǎng)度該如何解決
在Java中,由于字符串常量池的存在,String常量長(zhǎng)度限制取決于String常量在常量池中的存儲(chǔ)大小,下面這篇文章主要給大家介紹了關(guān)于java?String到底有多長(zhǎng)?String超出長(zhǎng)度該如何解決的相關(guān)資料,需要的朋友可以參考下2023-01-01Java日常練習(xí)題,每天進(jìn)步一點(diǎn)點(diǎn)(41)
下面小編就為大家?guī)?lái)一篇Java基礎(chǔ)的幾道練習(xí)題(分享)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧,希望可以幫到你2021-07-07Spring實(shí)戰(zhàn)之協(xié)調(diào)作用域不同步的Bean操作示例
這篇文章主要介紹了Spring實(shí)戰(zhàn)之協(xié)調(diào)作用域不同步的Bean操作,結(jié)合實(shí)例形式分析了Spring協(xié)調(diào)作用域不同步的Bean相關(guān)配置及使用技巧,需要的朋友可以參考下2019-11-11