線程池學習理解

1.線程池是什麼

爲了避免系統頻繁地創建和銷燬線程,我們可以讓創建的線程進行復用。如果大家進行過數據庫開發,對數據庫連接池應該不會陌生。爲了避免每次數據庫查詢都重新建立和銷燬數據庫連接,我們可以使用數據庫連接池維護一些數據庫連接,讓他們長期保持在一個激活狀態。當系統需要使用數據庫時,並不是創建一個新的連接,而是從連接池中獲得一個可用的連接即可。反之,當需要關閉連接時,並不真的把連接關閉,而是將這個連接“還”給連接池即可。通過這種方式,可以節約不少創建和銷燬對象的時間。

 

2.線程池的內部原理與實現

先了解一下線程池最重要的構造函數:

 

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

 

函數的參數含義如下。(如果不是太理解,可以跳過。一會回頭看會更好!)

• corePoolSize:指定了線程池中的線程數量。
• maximumPoolSize:指定了線程池中的最大線程數量。
• keepAliveTime:當線程池線程數量超過corePoolSize時,多餘的空閒線程的存活時間。即,超過corePoolSize的空閒線程,在多長時間內,會被銷燬。
• unit: keepAliveTime 的單位。
• workQueue:任務隊列,被提交但尚未被執行的任務。
• threadFactory:線程工廠,用於創建線程,一般用默認的即可。
• handler:拒絕策略。當任務太多來不及處理,如何拒絕任務。

 

 

接下來了解一下線程池的核心調度代碼(代碼結合圖去理解更好點)

 

        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);


 

簡單說就是當我們提交一個任務時,會判斷線程運行數量是否小於corePoolSize,小於就直接分配線程執行該任務,大於就去執行隊列(workQueue),如果隊列有位置則進入隊列,如果失敗再次查看線程池線程數量是否小於maximumPoolSize,小於則正價線程數數量執行該任務,如果達到maximumPoolSize則執行拒絕策略(handler)。

 

 

 

3.下面簡單介紹一下我們常見的隊列 模式

• 接提交的隊列:該功能由SynchronousQueue對象提供。SynchronousQueue是一個特殊的BlockingQueue。SynchronousQueue沒有容量,每一個插入操作都要等待一個相應的刪除操作,反之,每一個刪除操作都要等待對應的插入操作。如果使SynchronousQueue,提交的任務不會被真實的保存,而總是將新任務提交給線程執行,如果沒有空閒的進程,則嘗試創建新的進程.如果進程數裏已經達到最大值.則執行拒絕策略。因此,使用SynchronousQueue隊列.通常要設置很大的maximumPoolSize值,否則很容易執行拒絕策略。



•有界的任務隊列:有界的任務隊列可以使用ArrayBlockingQucue實現。ArrdyBlockingQueue的構造函數必須帶一個容量參數.表示該隊列的最大容量,當使用有界的任務隊列時,若有新的任務需要執行.如果線程池的實際線程數小於corcPoolSize.則會優先創建新的線程,若大於FcorePoolSize,則會將新任務加入等持隊列。若等待隊列已滿,無法加入,則在總線程數不大於maximumPoolSize的前提下,創建新的進程執行任務,若大於maximumPooiSizc,則執行櫃絕策略。可見,有界隊列僅當在任務隊列裝滿時.纔可能將線程數提升到corePoolSize以上,換言之.除非系統非常繁忙,否則確保核心線程數維持在corePooISize。

 

•無界的任務隊列:無界任務隊列可以通過 LinkedBlockingQueuc類實現。與有界隊列相比,除非系統資源耗盡,否則無界的任務隊列不存在任務入隊失敗的情況。當有新的任務到來,系統的線程數小 於corePoolSize時,線程池會生成新的線程執行任務,但當系統的線程數達到 corePoolSize後,就不會繼續增加。若後續仍有新的任務加入,而又沒有空閒的線程資源,則任務直接進入隊列等待。若任務創建和處理的速度差異很大,無界隊列會保持快速增長,直到耗盡系統內存。


•優先任務隊列:優先任務隊列是帶有執行優先級的隊列。它通過PriorityBlockingQueue實現,可以控制任務的執行先後順序。它是一個特殊的無界隊列。無論是有界隊列ArrayBlockingQueue,還是未指定大小的無界隊列LinkedBlockingQueue都是按照先進先出算法處理任務的。而PriorityBlockingQueue則可以根據任務自身的優先級順序先後執行,在確保系統性能的同時,也能有很好的質量保證(總是確保高優先級的任務先執行)。

 

4.拒絕策略:

JDK內置的拒絕策略如下。
• AbortPolicy策略:該策略會直接拋出異常,阻止系統正常工作。
• CallerRunsPolicy策略:只要線程池未關閉,該策略直接在調用者線程中,運行當前被丟棄的任務。顯然這樣做不會真的丟棄任務,但是,任務提交線程的性能極有可能會急劇下降。
• DiscardOledestPolicy策略:該策略將丟棄最老的一個請求,也就是即將被執行的一個任務,並嘗試再次提交當前任務。
• DiscardPolicy策略:該策略默默地丟棄無法處理的任務,不予任何處理。如果允許任務丟失,我覺得這可能是最好的一種方案了吧。

以上內置的策略均實現了RejectedExecutionHandler接口,若以上策略仍無法滿足實際應用需要,完全可以自己擴展 RejectedExecutionHandler 接口。

參考書籍:《實戰Java高併發程序設計    葛一鳴,郭超

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章