11111111111
知識共享平臺
知識共享平臺

討教大學(xué)平臺

  • 首頁
  • 免費課
  • 精品課
  • 討教題庫
  • 企業(yè)服務(wù)

    hot

  • 下載APP
  • 證書查詢
  • 關(guān)于我們
我問
討教號
搜索
消息
  • 我的文章

    我的關(guān)注

    我的問答

    我的秘密

    我的評論

    我的訂閱

    我的打賞

    我的錢包

    我的通知

    我的設(shè)置

    退出登錄

  • ×

    登錄

    討教 | 通行證

    登錄
    立即注冊
    忘記密碼?
    使用微信登錄

    提問 ×

    寫下你的問題,準確的表述更容易得到答案

    類型話題

    選擇支付方式
    您的討教幣 111 付費金額

                  沒那么簡單的線程池

                  JAVA葵花寶典
                  2019-06-26 17:19:51
                  16篇 作品
                  1991 總閱讀量

                  原以為線程池還挺簡單的(平時常用,也分析過原理),這次是想自己動手寫一個線程池來更加深入的了解它;但在動手寫的過程中落地到細節(jié)時發(fā)現(xiàn)并沒想的那么容易。結(jié)合源碼對比后確實不得不佩服 DougLea 。

                  我覺得大部分人直接去看 java.util.concurrent.ThreadPoolExecutor 的源碼時都是看一個大概,因為其中涉及到了許多細節(jié)處理,還有部分 AQS 的內(nèi)容,所以想要理清楚具體細節(jié)并不是那么容易。

                  與其挨個分析源碼不如自己實現(xiàn)一個簡版,當然簡版并不意味著功能缺失,需要保證核心邏輯一致。

                  所以也是本篇文章的目的:

                  自己動手寫一個五臟俱全的線程池,同時會了解到線程池的工作原理,以及如何在工作中合理的利用線程池。

                  再開始之前建議對線程池不是很熟悉的朋友看看這幾篇:

                  這里我截取了部分內(nèi)容,也許可以埋個伏筆(坑)。具體請看這兩個鏈接。

                  • 如何優(yōu)雅的使用和理解線程池

                  • 線程池中你不容錯過的一些細節(jié)

                  由于篇幅限制,本次可能會分為上下兩篇。

                  創(chuàng)建線程池

                  現(xiàn)在進入正題,新建了一個 CustomThreadPool 類,它的工作原理如下:簡單來說就是往線程池里邊丟任務(wù),丟的任務(wù)會緩沖到隊列里;線程池里存儲的其實就是一個個的 Thread ,他們會一直不停的從剛才緩沖的隊列里獲取任務(wù)執(zhí)行。

                  流程還是挺簡單。

                  先來看看我們這個自創(chuàng)的線程池的效果如何吧:初始化了一個核心為3、最大線程數(shù)為5、隊列大小為 4 的線程池。

                  先往其中丟了 10 個任務(wù),由于阻塞隊列的大小為 4 ,最大線程數(shù)為 5 ,所以由于隊列里緩沖不了最終會創(chuàng)建 5 個線程(上限)。

                  過段時間沒有任務(wù)提交后( sleep)則會自動縮容到三個線程(保證不會小于核心線程數(shù))。

                  構(gòu)造函數(shù)

                  來看看具體是如何實現(xiàn)的。

                  下面則是這個線程池的構(gòu)造函數(shù):會有以下幾個核心參數(shù):

                  • miniSize 最小線程數(shù),等效于 ThreadPool 中的核心線程數(shù)。

                  • maxSize 最大線程數(shù)。

                  • keepAliveTime 線程保活時間。

                  • workQueue 阻塞隊列。

                  • notify 通知接口。

                  大致上都和 ThreadPool 中的參數(shù)相同,并且作用也是類似的。

                  需要注意的是其中初始化了一個 workers 成員變量:

                  1. /**

                  2. * 存放線程池

                  3. */

                  4. private volatile Set<Worker> workers;


                  5. public CustomThreadPool(int miniSize, int maxSize, long keepAliveTime,

                  6. TimeUnit unit, BlockingQueue<Runnable> workQueue, Notify notify) {


                  7. workers = new ConcurrentHashSet<>();

                  8. }

                  workers 是最終存放線程池中運行的線程,在 j.u.c 源碼中是一個 HashSet 所以對他所有的操作都是需要加鎖。

                  我這里為了簡便起見就自己定義了一個線程安全的 Set 稱為 ConcurrentHashSet。其實原理也非常簡單,和 HashSet 類似也是借助于 HashMap 來存放數(shù)據(jù),利用其 key 不可重復(fù)的特性來實現(xiàn) set ,只是這里的 HashMap 是用并發(fā)安全的 ConcurrentHashMap 來實現(xiàn)的。

                  這樣就能保證對它的寫入、刪除都是線程安全的。

                  不過由于 ConcurrentHashMap 的 size() 函數(shù)并不準確,所以我這里單獨利用了一個 AtomicInteger 來統(tǒng)計容器大小。

                  創(chuàng)建核心線程

                  往線程池中丟一個任務(wù)的時候其實要做的事情還蠻多的,最重要的事情莫過于創(chuàng)建線程存放到線程池中了。

                  當然我們不能無限制的創(chuàng)建線程,不然拿線程池來就沒任何意義了。于是 miniSize maxSize這兩個參數(shù)就有了它的意義。

                  但這兩個參數(shù)再哪一步的時候才起到作用呢?這就是首先需要明確的。從這個流程圖可以看出第一步是需要判斷是否大于核心線程數(shù),如果沒有則創(chuàng)建。結(jié)合代碼可以發(fā)現(xiàn)在執(zhí)行任務(wù)的時候會判斷是否大于核心線程數(shù),從而創(chuàng)建線程。

                  worker.startTask() 執(zhí)行任務(wù)部分放到后面分析。

                  這里的 miniSize 由于會在多線程場景下使用,所以也用 volatile 關(guān)鍵字來保證可見性。

                  隊列緩沖一旦寫入失敗則會判斷當前線程池的大小是否大于最大線程數(shù),如果沒有則繼續(xù)創(chuàng)建線程執(zhí)行。

                  不然則執(zhí)行會嘗試阻塞寫入隊列( j.u.c 會在這里執(zhí)行拒絕策略)

                  以上的步驟和剛才那張流程圖是一樣的,這樣大家是否有看出什么坑嘛?

                  時刻小心從上面流程圖的這兩步可以看出會直接創(chuàng)建新的線程。

                  這個過程相對于中間直接寫入阻塞隊列的開銷是非常大的,主要有以下兩個原因:

                  • 創(chuàng)建線程會加鎖,雖說最終用的是 ConcurrentHashMap 的寫入函數(shù),但依然存在加鎖的可能。

                  • 會創(chuàng)建新的線程,創(chuàng)建線程還需要調(diào)用操作系統(tǒng)的 API 開銷較大。

                  所以理想情況下我們應(yīng)該避免這兩步,盡量讓丟入線程池中的任務(wù)進入阻塞隊列中。

                  執(zhí)行任務(wù)

                  任務(wù)是添加進來了,那是如何執(zhí)行的?

                  在創(chuàng)建任務(wù)的時候提到過 worker.startTask() 函數(shù):

                  1. /**

                  2. * 添加任務(wù),需要加鎖

                  3. * @param runnable 任務(wù)

                  4. */

                  5. private void addWorker(Runnable runnable) {

                  6. Worker worker = new Worker(runnable, true);

                  7. worker.startTask();

                  8. workers.add(worker);

                  9. }

                  也就是在創(chuàng)建線程執(zhí)行任務(wù)的時候會創(chuàng)建 Worker 對象,利用它的 startTask() 方法來執(zhí)行任務(wù)。

                  所以先來看看 Worker 對象是長啥樣的:其實他本身也是一個線程,將接收到需要執(zhí)行的任務(wù)存放到成員變量 task 處。

                  而其中最為關(guān)鍵的則是執(zhí)行任務(wù) worker.startTask() 這一步驟。

                  1. public void startTask() {

                  2. thread.start();

                  3. }

                  其實就是運行了 worker 線程自己,下面來看 run 方法。

                  • 第一步是將創(chuàng)建線程時傳過來的任務(wù)執(zhí)行( task.run),接著會一直不停的從隊列里獲取任務(wù)執(zhí)行,直到獲取不到新任務(wù)了。

                  • 任務(wù)執(zhí)行完畢后將內(nèi)置的計數(shù)器 -1 ,方便后面任務(wù)全部執(zhí)行完畢進行通知。

                  • worker 線程獲取不到任務(wù)后退出,需要將自己從線程池中釋放掉( workers.remove(this))。

                  從隊列里獲取任務(wù)

                  其實 getTask 也是非常關(guān)鍵的一個方法,它封裝了從隊列中獲取任務(wù),同時對不需要保活的線程進行回收。很明顯,核心作用就是從隊列里獲取任務(wù);但有兩個地方需要注意:

                  • 當線程數(shù)超過核心線程數(shù)時,在獲取任務(wù)的時候需要通過保活時間從隊列里獲取任務(wù);一旦獲取不到任務(wù)則隊列肯定是空的,這樣返回 null 之后在上文的 run() 中就會退出這個線程;從而達到了回收線程的目的,也就是我們之前演示的效果 

                    關(guān)閉線程池

                    最后來談?wù)劸€程關(guān)閉的事;還是以剛才那段測試代碼為例,如果提交任務(wù)后我們沒有關(guān)閉線程,會發(fā)現(xiàn)即便是任務(wù)執(zhí)行完畢后程序也不會退出。

                    從剛才的源碼里其實也很容易看出來,不退出的原因是 Worker 線程一定還會一直阻塞在 task=workQueue.take(); 處,即便是線程縮容了也不會小于核心線程數(shù)。

                    通過堆棧也能證明:恰好剩下三個線程阻塞于此處。

                    而關(guān)閉線程通常又有以下兩種:

                    • 立即關(guān)閉:執(zhí)行關(guān)閉方法后不管現(xiàn)在線程池的運行狀況,直接一刀切全部停掉,這樣會導(dǎo)致任務(wù)丟失。

                    • 不接受新的任務(wù),同時等待現(xiàn)有任務(wù)執(zhí)行完畢后退出線程池。

                    立即關(guān)閉

                    我們先來看第一種 立即關(guān)閉:

                    1. /**

                    2. * 立即關(guān)閉線程池,會造成任務(wù)丟失

                    3. */

                    4. public void shutDownNow() {

                    5. isShutDown.set(true);

                    6. tryClose(false);

                    7. }


                    8. /**

                    9. * 關(guān)閉線程池

                    10. *

                    11. * @param isTry true 嘗試關(guān)閉 --> 會等待所有任務(wù)執(zhí)行完畢

                    12. * false 立即關(guān)閉線程池--> 任務(wù)有丟失的可能

                    13. */

                    14. private void tryClose(boolean isTry) {

                    15. if (!isTry) {

                    16. closeAllTask();

                    17. } else {

                    18. if (isShutDown.get() && totalTask.get() == 0) {

                    19. closeAllTask();

                    20. }

                    21. }


                    22. }


                    23. /**

                    24. * 關(guān)閉所有任務(wù)

                    25. */

                    26. private void closeAllTask() {

                    27. for (Worker worker : workers) {

                    28. //LOGGER.info("開始關(guān)閉");

                    29. worker.close();

                    30. }

                    31. }


                    32. public void close() {

                    33. thread.interrupt();

                    34. }

                    很容易看出,最終就是遍歷線程池里所有的 worker 線程挨個執(zhí)行他們的中斷函數(shù)。

                    我們來測試一下:可以發(fā)現(xiàn)后面丟進去的三個任務(wù)其實是沒有被執(zhí)行的。

                    完事后關(guān)閉

                    而正常關(guān)閉則不一樣:

                    1. /**

                    2. * 任務(wù)執(zhí)行完畢后關(guān)閉線程池

                    3. */

                    4. public void shutdown() {

                    5. isShutDown.set(true);

                    6. tryClose(true);

                    7. }來看看實際效果:回收線程


                      上文或多或少提到了線程回收的事情,其實總結(jié)就是以下兩點:

                      一旦執(zhí)行了 shutdown/shutdownNow 方法都會將線程池的狀態(tài)置為關(guān)閉狀態(tài),這樣只要 worker 線程嘗試從隊列里獲取任務(wù)時就會直接返回空,導(dǎo)致 worker 線程被回收。

                      但如果我們的隊列足夠大,導(dǎo)致線程數(shù)都不會超過核心線程數(shù),這樣是不會觸發(fā)回收的。

                      比如這里我將隊列大小調(diào)為 10 ,這樣任務(wù)就會累計在隊列里,不會創(chuàng)建五個 worker 線程。

                      所以一直都是 Thread-1~3 這三個線程在反復(fù)調(diào)度任務(wù)。

                      總結(jié)

                      本次實現(xiàn)了線程池里大部分核心功能,我相信只要看完并動手敲一遍一定會對線程池有不一樣的理解。

                      結(jié)合目前的內(nèi)容來總結(jié)下:

                      • 線程池、隊列大小要設(shè)計的合理,盡量的讓任務(wù)從隊列中獲取執(zhí)行。

                      • 慎用 shutdownNow() 方法關(guān)閉線程池,會導(dǎo)致任務(wù)丟失(除非業(yè)務(wù)允許)。

                      • 如果任務(wù)多,線程執(zhí)行時間短可以調(diào)大 keepalive 值,使得線程盡量不被回收從而可以復(fù)用線程。

                      同時下次會分享一些線程池的新特性,如:

                      • 執(zhí)行帶有返回值的線程。

                      • 異常處理怎么辦?

                      • 所有任務(wù)執(zhí)行完怎么通知我?

                  本網(wǎng)站內(nèi)容僅代表作者本人的觀點,不代表本網(wǎng)站的觀點和看法,與本網(wǎng)站立場無關(guān),如有侵權(quán)請聯(lián)系討教。
                  給作者打賞,鼓勵TA抓緊創(chuàng)作
                  0人打賞金額
                  JAVA葵花寶典
                  16篇 作品
                  1991 總閱讀量
                  評論
                  您可能感興趣的文章

                  項目管理服務(wù)模式

                  敏捷項目管理與傳統(tǒng)項目管理比較

                  項目管理的特點

                  PMO是什么?是管項目經(jīng)理的嘛?

                  項目經(jīng)理必須關(guān)注的開會十大關(guān)鍵問題!

                  項目的組成要素

                  熱門話題 更多話題
                  精益生產(chǎn) 質(zhì)量管理 智能制造
                  職場效率 項目管理 討教
                  AI 大數(shù)據(jù) 六西格瑪
                  ×

                  給作者打賞,鼓勵TA抓緊創(chuàng)作!

                  選擇支付方式
                  選擇打賞金額
                  注:打賞的收益歸作者,非平臺

                  微信掃描支付

                  打賞金額: 1元

                  ×

                  給作者打賞,鼓勵TA抓緊創(chuàng)作!

                  您的討教幣
                  填寫您打賞討教幣數(shù)量
                  輸入密碼

                  111

                  注:打賞的收益歸作者,非平臺

                  微信掃描支付

                  打賞金額: 1元

                  感谢您访问我们的网站,您可能还对以下资源感兴趣:

                  国产精品久久久久久久人人看
                  主站蜘蛛池模板: 清纯唯美经典一区二区| 国产一区中文字幕在线观看| 日韩好片一区二区在线看| 色欲综合一区二区三区| 无码人妻精品一区二区蜜桃百度 | 国产伦精品一区二区三区精品| 香蕉视频一区二区三区| 国产在线第一区二区三区| 在线成人一区二区| 精品无码AV一区二区三区不卡| 亚洲色精品vr一区二区三区| 一区二区三区视频网站| 视频一区二区在线播放| 日韩伦理一区二区| 国模无码视频一区二区三区| 伊人色综合视频一区二区三区| 精品国产福利一区二区| 国产精品美女一区二区视频| 精品国产一区二区三区久久| 99精品高清视频一区二区| 狠狠做深爱婷婷综合一区| 精品国产亚洲一区二区在线观看| 国产一区二区三区内射高清| 波多野结衣中文一区| 日本精品一区二区在线播放| 国产精品日韩一区二区三区 | 日本精品一区二区三区在线观看| 香蕉免费看一区二区三区| 激情综合一区二区三区| 精品一区二区三区四区| 人妻夜夜爽天天爽爽一区| 国产精品高清一区二区三区不卡 | 性色AV一区二区三区无码| 亚洲欧美日韩国产精品一区 | 亚洲愉拍一区二区三区| 国内精品视频一区二区三区八戒| 狠狠综合久久av一区二区| 国产激情一区二区三区在线观看 | 国产凹凸在线一区二区| 久久精品无码一区二区三区 | 一区二区三区精品视频|