你好,我是miniluo,不少面試官,老是喜歡問JDK線程池相關的問題,包括建立線程池的方式,線程池的參數等;做爲應聘者的你總會被這些問題所困擾。今天我就和你一塊兒學習Java線程池的相關知識,手動建立線程池。java
咱們都知道JDK有四種建立線程池的方式,它們在不一樣的場景下都有其適用性。可是《阿里巴巴 Java 開發手冊》開發規範明確規定不能使用Executors建立線程池;我也有和你同樣的疑惑,接下來咱們就一塊兒分析分析。面試
JDK建立線程池最經常使用的四種方式分別是:緩存
一、newFixedThreadPool(int nThreads)建立固定數據線程dom
二、newCachedThreadPool()建立一個可緩存的線程池,調用execute將重用之前構成的線程。學習
三、newSingleThreadPoolExecutor()建立一個單線程的線程池google
四、newScheduledThreadPool(int corePoolSize)建立一個定時及週期性執行任務的線程池spa
從上面的描述來看咱們很容易知道各自適應不一樣場景,使用起來也很方便。咱們先來看newFixedThreadPool什麼狀況下會出現OOM。線程
爲了更快看到OOM,咱們先修改JVM最小和最大內存分別是-Xms128m -Xmx512m,以後咱們執行下面代碼日誌
@Slf4j public class ExecutorsTest { public static void main(String[] args) { ThreadPoolExecutor threadPool = (ThreadPoolExecutor) Executors.newFixedThreadPool(1); printThreadInfo(threadPool); for (int i = 0; i < 100000000; i++) { threadPool.execute(() -> { String randomValue = IntStream.rangeClosed(1, 1000000).mapToObj(test-> "c") .collect(Collectors.joining("")) + UUID.randomUUID().toString(); try { TimeUnit.HOURS.sleep(1); } catch (InterruptedException e) { } log.info(randomValue); }); } threadPool.shutdown(); try { threadPool.awaitTermination(1, TimeUnit.HOURS); } catch (InterruptedException e) { e.printStackTrace(); } } private static void printThreadInfo(ThreadPoolExecutor threadPool) { Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> { log.info("========================="); log.info("Pool Size: {}", threadPool.getPoolSize()); log.info("Active Threads: {}", threadPool.getActiveCount()); log.info("Number of Tasks Completed: {}", threadPool.getCompletedTaskCount()); log.info("Number of Tasks in Queue: {}", threadPool.getQueue().size()); log.info("========================="); }, 0, 1, TimeUnit.SECONDS); } }}
讓代碼跑一會,喝口水回來咱們發現就出現如圖所示的異常。
code
喝口水的工夫就出現OOM了,這要是擱生產那你電話就響了,那就是事故,得寫個事故報告了吧。
咱們翻看newFixedThreadPool方法的源碼發現,線程池的工做隊列直接new了一個LinkedBlockingQueue,而LinkedBlockingQueue默認的構造方法是一個長度爲Integer.MAX_VALUE的隊列,即無界隊列(直到耗盡全部JVM內存)。
newFixedThreadPool雖然固定線程數量,CPU沒有耗盡但是由於隊列的無界則可能會擠爆內存致使OOM。
咱們把建立線程池的方法修改成以下:
ThreadPoolExecutor threadPool = (ThreadPoolExecutor) Executors.newCachedThreadPool();
讓程序跑一會,站起來扭下脖子再瞄下就出現OOM了,如圖所示。
再翻看下newCachedThreadPool的源碼,其默認的線程大小爲Integer.MAX_VALUE即無限制,可是其工做隊列SynchronousQueue,是一個無存儲空間的FIFO隊列。線程是須要分配必定的存儲空間做爲線程棧,案例中咱們模擬每一個線程處理時長爲1h,因此就會建立不少的線程,加入每一個1MB,則按咱們上文所設置的JVM內存大小,就垂手可得的發送了OOM(固然生產若存在佔用線程1h的狀況就應該拿出來分析了,這裏是爲了模擬OOM)。
至此,咱們對阿里爲什麼強制禁止使用JDK建立線程池的方式有了一些理解。
下面咱們來講說手動創建線程池的好處,大概有如下幾點:
一、經過ThreadPoolExecutor的方式建立線程池,讓更加明確線程池的運行規則,規避資源耗盡的風險。
二、經過實現ThreadFactory,咱們能夠自定義有意義的線程名字,方便在出現問題的時候進行排查(特別是生產問題)。
三、經過實現RejectedExecutionHandler,咱們能夠作一些日誌記錄,有利於分析線程池的運行情況。
因爲手動建立線程池的代碼過多就不在這裏粘貼,有須要的能夠在留言處寫下郵箱,我逐一發送;後面我會把相關案例源碼上傳到GitHub供你們學習參考。
除了經過ThreadPoolExecutor建立線程池以外,還能經過commons-lang3和com.google.guava這兩個包提供的方法建立,有興趣的朋友能夠了解下。
思考和討論
一、或許你會以爲疑惑,那JDK提供的四種快捷建立線程池的方式目的是什麼?
二、另外兩種線程池所使用的工做隊列是什麼?
三、線程池核心最小核心線程數的設定規則有哪些?
歡迎留言與我分享和指正!也歡迎你把這篇文章分享給你的朋友或同事,一塊兒交流。
感謝您的閱讀,咱們下節再見!
掃碼關注咱們,與君共進