java線程池原理

為什么要使用線程池

  • 減少線程實例的創(chuàng)建和銷毀,使線程能夠重用谴古,提高系統(tǒng)性能

  • 可以根據(jù)系統(tǒng)的承受能力,調(diào)整線程池中工作線程的數(shù)量,防止線程消耗過多內(nèi)存導致服務器崩潰

線程池的實現(xiàn)原理

  1. 判斷線程池里的核心線程是否都在執(zhí)行任務经磅,如果不是(核心線程空閑或者還有核心線程沒有被創(chuàng)建)則創(chuàng)建一個新的工作線程來執(zhí)行任務。如果核心線程都在執(zhí)行任務钮追,則進入下個流程预厌。

  2. 線程池判斷工作隊列是否已滿,如果工作隊列沒有滿元媚,則將新提交的任務存儲在這個工作隊列里轧叽。如果工作隊列滿了苗沧,則進入下個流程。

  3. 判斷線程池里的線程是否都處于工作狀態(tài)炭晒,如果沒有待逞,則創(chuàng)建一個新的工作線程來執(zhí)行任務。如果已經(jīng)滿了网严,則交給飽和策略來處理這個任務贬墩。

流程圖如下

image

JDK原生線程池的創(chuàng)建


    public ThreadPoolExecutor(int corePoolSize,

                              int maximumPoolSize,

                              long keepAliveTime,

                              TimeUnit unit,

                              BlockingQueue<Runnable> workQueue,

                              RejectedExecutionHandler handler) {

        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,

            Executors.defaultThreadFactory(), handler);

    }

參數(shù)說明
  • corePoolSize:線程池核心線程數(shù)量

  • maximumPoolSize:線程池最大線程數(shù)量

  • keepAliverTime:當活躍線程數(shù)大于核心線程數(shù)時插掂,空閑的多余線程最大存活時間

  • unit:存活時間的單位

  • workQueue:存放任務的隊列

  • handler:超出線程范圍和隊列容量的任務的處理程序

線程池的源碼解讀


public void execute(Runnable command) {

        if (command == null)

            throw new NullPointerException();



        * 進行下面三步

        *

        * 1. 如果運行的線程小于corePoolSize,則嘗試使用用戶定義的Runnalbe對象創(chuàng)建一個新的線程

        *    調(diào)用addWorker函數(shù)會原子性的檢查runState和workCount鳞芙,通過返回false來防止在不應

        *    該添加線程時添加了線程

        * 2. 如果一個任務能夠成功入隊列骚灸,在添加一個線城時仍需要進行雙重檢查(因為在前一次檢查后

        *    該線程死亡了),或者當進入到此方法時驴一,線程池已經(jīng)shutdown了休雌,所以需要再次檢查狀態(tài),

        *    若有必要肝断,當停止時還需要回滾入隊列操作杈曲,或者當線程池沒有線程時需要創(chuàng)建一個新線程

        * 3. 如果無法入隊列,那么需要增加一個新線程胸懈,如果此操作失敗担扑,那么就意味著線程池已經(jīng)shut

        *    down或者已經(jīng)飽和了,所以拒絕任務

        */



        // 獲取線程池控制狀態(tài)

        int c = ctl.get();

        if (workerCountOf(c) < corePoolSize) { //worker數(shù)量小于corePoolSize

            if (addWorker(command, true))

                return;

            // 不成功則再次獲取線程池控制狀態(tài)

            c = ctl.get();

        }

        // 線程池處于RUNNING狀態(tài)趣钱,將命令(用戶自定義的Runnable對象)添加進workQueue隊列

        if (isRunning(c) && workQueue.offer(command)) {

        // 再次檢查涌献,獲取線程池控制狀態(tài)

            int recheck = ctl.get();



            // 線程池不處于RUNNING狀態(tài),將命令從workQueue隊列中移除

            if (! isRunning(recheck) && remove(command))

                reject(command);// 拒絕執(zhí)行命令

            else if (workerCountOf(recheck) == 0)// worker數(shù)量等于0

                // 添加worker

                addWorker(null, false);

        }

        // 添加worker失敗

        else if (!addWorker(command, false))

            reject(command);// 拒絕執(zhí)行命令

    }


private boolean addWorker(Runnable firstTask, boolean core) {

        retry:

        for (;;) { // 外層無限循環(huán)

            // 獲取線程池控制狀態(tài)

            int c = ctl.get();

            // 獲取狀態(tài)

            int rs = runStateOf(c);

            // Check if queue empty only if necessary.

            if (rs >= SHUTDOWN &&            // 狀態(tài)大于等于SHUTDOWN首有,初始的ctl為RUNNING燕垃,小于SHUTDOWN

                ! (rs == SHUTDOWN &&        // 狀態(tài)為SHUTDOWN

                  firstTask == null &&        // 第一個任務為null

                  ! workQueue.isEmpty()))    // worker隊列不為空

                // 返回

                return false;

            for (;;) {

                // worker數(shù)量

                int wc = workerCountOf(c);

                if (wc >= CAPACITY ||                                // worker數(shù)量大于等于最大容量

                    wc >= (core ? corePoolSize : maximumPoolSize))    // worker數(shù)量大于等于核心線程池大小或者最大線程池大小

                    return false;

                if (compareAndIncrementWorkerCount(c))                // 比較并增加worker的數(shù)量

                    // 跳出外層循環(huán)

                    break retry;

                // 獲取線程池控制狀態(tài)

                c = ctl.get();  // Re-read ctl

                if (runStateOf(c) != rs) // 此次的狀態(tài)與上次獲取的狀態(tài)不相同

                    // 跳過剩余部分,繼續(xù)循環(huán)

                    continue retry;

                // else CAS failed due to workerCount change; retry inner loop

            }

        }

        // worker開始標識

        boolean workerStarted = false;

        // worker被添加標識

        boolean workerAdded = false;

        //

        Worker w = null;

        try {

            // 初始化worker

            w = new Worker(firstTask);

            // 獲取worker對應的線程

            final Thread t = w.thread;

            if (t != null) { // 線程不為null

                // 線程池鎖

                final ReentrantLock mainLock = this.mainLock;

                // 獲取鎖

                mainLock.lock();

                try {

                    // Recheck while holding lock.

                    // Back out on ThreadFactory failure or if

                    // shut down before lock acquired.

                    // 線程池的運行狀態(tài)

                    int rs = runStateOf(ctl.get());

                    if (rs < SHUTDOWN ||                                    // 小于SHUTDOWN

                        (rs == SHUTDOWN && firstTask == null)) {            // 等于SHUTDOWN并且firstTask為null

                        if (t.isAlive()) // precheck that t is startable    // 線程剛添加進來井联,還未啟動就存活

                            // 拋出線程狀態(tài)異常

                            throw new IllegalThreadStateException();

                        // 將worker添加到worker集合

                        workers.add(w);

                        // 獲取worker集合的大小

                        int s = workers.size();

                        if (s > largestPoolSize) // 隊列大小大于largestPoolSize

                            // 重新設置largestPoolSize

                            largestPoolSize = s;

                        // 設置worker已被添加標識

                        workerAdded = true;

                    }

                } finally {

                    // 釋放鎖

                    mainLock.unlock();

                }

                if (workerAdded) { // worker被添加

                    // 開始執(zhí)行worker的run方法

                    t.start();

                    // 設置worker已開始標識

                    workerStarted = true;

                }

            }

        } finally {

            if (! workerStarted) // worker沒有開始

                // 添加worker失敗

                addWorkerFailed(w);

        }

        return workerStarted;

    }

說明:此函數(shù)可能會完成如下幾件任務

  1. 原子性的增加workerCount卜壕。

  2. 將用戶給定的任務封裝成為一個worker,并將此worker添加進workers集合中烙常。

  3. 啟動worker對應的線程轴捎,并啟動該線程,運行worker的run方法蚕脏。

  4. 回滾worker的創(chuàng)建動作侦副,即將worker從workers集合中刪除,并原子性的減少workerCount蝗锥。

我們通過一個程序來觀察線程池的工作原理:


public class ThreadPoolTest {

    public static void main(String[] args) {

        BlockingQueue<Runnable> blockingQueue = new LinkedBlockingDeque<>(5);

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 60, TimeUnit.SECONDS, blockingQueue);

        for (int i = 0; i < 10; i++) {

            threadPoolExecutor.execute(() -> {

                try {

                    Thread.sleep(100);

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

            });

            System.out.println("線程池中活躍的線程數(shù): " + threadPoolExecutor.getPoolSize());

            if (blockingQueue.size() > 0) {

                System.out.println("----------------隊列中阻塞的線程數(shù)" + blockingQueue.size());

            }

        }

    }

}

執(zhí)行結果如下


線程池中活躍的線程數(shù): 1

線程池中活躍的線程數(shù): 2

線程池中活躍的線程數(shù): 3

線程池中活躍的線程數(shù): 4

線程池中活躍的線程數(shù): 5

線程池中活躍的線程數(shù): 5

----------------隊列中阻塞的線程數(shù)1

線程池中活躍的線程數(shù): 5

----------------隊列中阻塞的線程數(shù)2

線程池中活躍的線程數(shù): 5

----------------隊列中阻塞的線程數(shù)3

線程池中活躍的線程數(shù): 5

----------------隊列中阻塞的線程數(shù)4

線程池中活躍的線程數(shù): 5

----------------隊列中阻塞的線程數(shù)5

線程池中活躍的線程數(shù): 6

----------------隊列中阻塞的線程數(shù)5

線程池中活躍的線程數(shù): 7

----------------隊列中阻塞的線程數(shù)5

線程池中活躍的線程數(shù): 8

----------------隊列中阻塞的線程數(shù)5

線程池中活躍的線程數(shù): 9

----------------隊列中阻塞的線程數(shù)5

線程池中活躍的線程數(shù): 10

----------------隊列中阻塞的線程數(shù)5

Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task thread.ThreadPoolTest$$Lambda$1/1313922862@27bc2616 rejected from java.util.concurrent.ThreadPoolExecutor@3941a79c[Running, pool size = 10, active threads = 10, queued tasks = 5, completed tasks = 0]

at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)

at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)

at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)

at thread.ThreadPoolTest.main(ThreadPoolTest.java:18)

從結果可以觀察出:

  1. 創(chuàng)建的線程池具體配置為:核心線程數(shù)量為5個跃洛;全部線程數(shù)量為10個;工作隊列的長度為5终议。

  2. 我們通過queue.size()的方法來獲取工作隊列中的任務數(shù)。

  3. 運行原理:

    剛開始都是在創(chuàng)建新的線程,達到核心線程數(shù)量5個后穴张,新的任務進來后不再創(chuàng)建新的線程细燎,而是將任務加入工作隊列,任務隊列到達上線5個后皂甘,新的任務又會創(chuàng)建新的普通線程玻驻,直到達到線程池最大的線程數(shù)量10個,后面的任務則根據(jù)配置的飽和策略來處理偿枕。我們這里沒有具體配置璧瞬,使用的是默認的配置AbortPolicy:直接拋出異常。

當然渐夸,為了達到我需要的效果嗤锉,上述線程處理的任務都是利用休眠導致線程沒有釋放!D顾瘟忱!

RejectedExecutionHandler:飽和策略

$ 隊列和線程池都滿了,說明線程池處于飽和狀態(tài)苫幢,那么必須對新提交的任務采用一種特殊的策略來進行處理访诱。這個策略默認配置是AbortPolicy,表示無法處理新的任務而拋出異常韩肝。JAVA提供了4中策略:

  1. AbortPolicy:直接拋出異常

  2. CallerRunsPolicy:只用調(diào)用所在的線程運行任務

  3. DiscardOldestPolicy:丟棄隊列里最近的一個任務触菜,并執(zhí)行當前任務。

  4. DiscardPolicy:不處理哀峻,丟棄掉涡相。

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市谜诫,隨后出現(xiàn)的幾起案子漾峡,更是在濱河造成了極大的恐慌,老刑警劉巖喻旷,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件生逸,死亡現(xiàn)場離奇詭異,居然都是意外死亡且预,警方通過查閱死者的電腦和手機槽袄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來锋谐,“玉大人遍尺,你說我怎么就攤上這事′剔郑” “怎么了乾戏?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵迂苛,是天一觀的道長。 經(jīng)常有香客問我鼓择,道長三幻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任呐能,我火速辦了婚禮念搬,結果婚禮上,老公的妹妹穿的比我還像新娘摆出。我一直安慰自己朗徊,他們只是感情好,可當我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布偎漫。 她就那樣靜靜地躺著爷恳,像睡著了一般。 火紅的嫁衣襯著肌膚如雪骑丸。 梳的紋絲不亂的頭發(fā)上舌仍,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天,我揣著相機與錄音通危,去河邊找鬼铸豁。 笑死,一個胖子當著我的面吹牛菊碟,可吹牛的內(nèi)容都是我干的节芥。 我是一名探鬼主播,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼逆害,長吁一口氣:“原來是場噩夢啊……” “哼头镊!你這毒婦竟也來了?” 一聲冷哼從身側響起魄幕,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤相艇,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后纯陨,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體坛芽,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年翼抠,在試婚紗的時候發(fā)現(xiàn)自己被綠了咙轩。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡阴颖,死狀恐怖活喊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情量愧,我是刑警寧澤钾菊,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布帅矗,位于F島的核電站,受9級特大地震影響结缚,放射性物質(zhì)發(fā)生泄漏损晤。R本人自食惡果不足惜软棺,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一红竭、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧喘落,春花似錦茵宪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至赌朋,卻和暖如春凰狞,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背沛慢。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工赡若, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人团甲。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓逾冬,卻偏偏與公主長得像,于是被迫代替她去往敵國和親躺苦。 傳聞我的和親對象是個殘疾皇子身腻,可洞房花燭夜當晚...
    茶點故事閱讀 45,092評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 在我們的開發(fā)中“池”的概念并不罕見,有數(shù)據(jù)庫連接池匹厘、線程池嘀趟、對象池、常量池等等愈诚。下面我們主要針對線程池來一步一步揭...
    Java架構閱讀 4,080評論 4 87
  • 前言:線程是稀缺資源她按,如果被無限制的創(chuàng)建,不僅會消耗系統(tǒng)資源扰路,還會降低系統(tǒng)的穩(wěn)定性尤溜,合理的使用線程池對線程進行統(tǒng)一...
    SDY_0656閱讀 716評論 0 1
  • 第一部分 來看一下線程池的框架圖宫莱,如下: 1、Executor任務提交接口與Executors工具類 Execut...
    壓抑的內(nèi)心閱讀 4,268評論 1 24
  • 清晨哩罪,美好的清晨授霸,我們這群剛剛出土的苗苗巡验,抬頭仰望五彩繽紛的天空―一幅壯麗的祖國未來的藍圖,拿起知識的畫筆去描繪碘耳,...
    紅黃藍圖圖班閱讀 289評論 0 0
  • 毛公上天一巨龍显设, 澤福人民潤眾生。 東西南北誰人比辛辨? 思維出神戰(zhàn)日寇捕捂, 想法大膽斗蔣公; 永往直前抗美帝, 放眼全...
    遠方的記憶閱讀 298評論 2 1