java 线程执行完就会回收吗_Java线程池技术Executors的这个坑你踩过吗?
線程池技術是Java的一大特性,如果我們想要編寫高并發、高吞吐的程序,線程池的技術使用是必須的。對于很多程序員來說,多線程和線程池技術都了然于胸,基本原理和使用都數量掌握,分分鐘可以寫出一個生產消費者模式的多線程,也能寫出線程集合和線程相互等待的業務功能。但是,在使用多線程和線程池技術的過程中,我們會碰到各種各樣詭異的問題,這種問題會莫名其妙的出現,我們很難定位。本文就示例說明關于阻塞線程的一大坑,各位Java程序員你是否踩過?我們應該如何避免呢?
四種線程池策略
Java的JUC包中有四種內置的創建線程池的策略,借助這些方策略,我們可以很方便的創建一個線程池對象在業務中使用。這四個策略分別為:
- 1、NewSingleThreadExecutor,單線程的線程池
單線程的線程池中只有一個線程,也就是所有任務都是通過這一個線程來串行執行,保證了執行任務按照提交順序來執行。如果業務只是涉及到異步執行,可選擇該線程池技術。
- 2、NewFixedThreadPool,固定數量線程的線程池
固定大小的線程池創建的時候限制了線程池中線程的數量。當有新的任務創建的時候,就創建一個新的線程來執行任務。當線程池中的線程數量達到設定的值的時候,任務開始等待執行,直到有可用的線程。
- 3、NewCachedThreadPool,可緩存的線程池
可緩存的線程池不限定線程數量,當有新的任務加入,如果沒有可用的線程,那就創建新的線程。線程最大數量取決于操作系統所能創建的最大線程數量。此線程池技術也是項目開發中不被推薦的一種方法,因為如何控制不好任務數量,將會導致大量的線程被創建,影響系統的性能。
- 4、NewScheduledThreadPool,定時執行的線程池
定時執行的線程池支持創建無線數量的線程池,并且線程按照設定的時間周期執行。
這是四種線程池策略。我們可以通過JUC的相關API來創建相應的線程池,具體需要創建什么樣的線程池策略,根據業務需求。如果只是讓某個業務異步執行,那就使用NewSingleThreadExecutor,如果想定義固定線程數量那就使用NewFixedThreadPool,如果是啟動定時任務來執行業務邏輯,那就使用NewScheduledThreadPool。
Executors導致的”大坑“
使用這四種線程池技術看似簡單,通過相應的API就能快速創建。就是因為JUC API多于線程池技術的封裝,導致我們在使用線程池技術會遇到一大坑。
Executors是JUC中一個核心的創建線程池的工具類,通過Executors我們可以快速的創建不同策略的線程池。Executors中的方法如下所示:
我們可以看到Executors類中提供了很多方法用來創建線程池。例如:我們要創建一個固定大小的線程池,示例代碼如下:
那這段代碼有沒有什么問題呢?沒有問題。但是,如果是在正常的業務流程中,就會有很大的問題,我們舉一個實際的業務場景:
某個業務系統中,需要消費Kafka中的消息,然后將消息進行處理存庫。Kafka消費消息的速度很快,但是入庫的速度很慢,因為是消費和入庫是同步執行的,導致Kafka的消息有擠壓。所以我們需要將消費和入庫分開異步執行,異步執行我們想到了使用線程池技術,我們可以開啟固定數量線程的線程池技策略來多線程的入庫。我們模擬一下場景代碼:
通過測試發現,100次的循環很快結束,(即100條kafka消息很快消費完成)。所有的待處理任務全部加入了executorService的待處理隊列任務中。如果循環1000次,executorService的隊列中有大量的待處理任務,從而導致系統OOM。
通過研究源碼我們可以發現,Executors.newFixedThreadPool(10)內部調用的ThreadPoolExecutor默認采用的阻塞隊列是LinkedBlockingQueue,并且的它的長度是隊列長度是Integer.MAX_VALUE,即2147483647。這就是導致堆積大量的請求,系統發生OOM的原因。
這就是用Executors創建線程池的一大坑,它隱藏了內部的實現細節。如果我們直接使用Executors來創建線程池,這對于高并發系統來說就是一場災難。
ThreadPoolExecutor創建線程池
那回到上面的例子,我們如何解決這個問題呢?通過ThreadPoolExecutor直接來創建線程池,示例代碼如下:
在ThreadPoolExecutor的構造方法中,我們可以定義阻塞隊列的大小,這樣就不會讓線程池隊列無限大而導致OOM了。但是,這里面又有一個坑:當阻塞隊列滿員,后面要再加入任務,會報錯,如下錯誤所示:
ArrayBlockingQueue Offer方法的”坑“
通過研究源碼我們發現,ThreadPoolExecutor中阻塞隊列添加任務時采用的是offer方法,如下圖所示:
我們都知道,LinkedBlockingQueue、ArrayBlockingQueue以及其他阻塞隊列,offer方法在添加元素的時候,如果隊列已滿無法添加元素的時候,offer方法會直接返回false,這就會導致上面的異常。
自定義阻塞隊列
那如果我們要實現一個阻塞線程池隊列,我們改如何實現呢?自定義阻塞隊列,覆寫offer方法,示例代碼如下:
自定義阻塞隊列:
對于put方法,若向隊尾添加元素的時候發現隊列已經滿了會發生阻塞,這正好滿足我們的需求。改造后的代碼如下:
通過運行我們發現滿足我們的需求。
不會堆積大量的隊列任務。
總結
Java線程池技術在業務中需要慎重使用,對于線程池技術的內部實現機制我們需要精通掌握,才能在實際項目中熟練使用。文中涉及到的”坑“大家還需要好好消化理解,在以后的項目開發中盡量避免。
總結
以上是生活随笔為你收集整理的java 线程执行完就会回收吗_Java线程池技术Executors的这个坑你踩过吗?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 小程序云服务器选什么系统好,小程序云服务
- 下一篇: windows分屏_windows内到底