Java線程池是如何實現(xiàn)線程復(fù)用的鸥鹉?

前言

沒看本文,面試掛了庶骄,別說沒提醒你毁渗!
沒看本文,面試掛了单刁,別說沒提醒你灸异!
沒看本文,面試掛了羔飞,別說沒提醒你肺樟!

相信很多人都接觸過線程池,我們知道線程池有核心線程和非核心線程之分逻淌,其中核心線程是一直存活在線程池中的么伯,而非核心線程是在執(zhí)行完任務(wù)之后超時銷毀的。但是大家應(yīng)該都知道一點卡儒,當(dāng)Thread執(zhí)行完Runnable任務(wù)之后就會銷毀田柔,而且就算執(zhí)行完任務(wù)之后把線程掛起也沒有辦法再去執(zhí)行其他任務(wù),那線程池是如何做到核心線程復(fù)用的呢骨望?下面就通過閱讀源碼的方法帶大家了解背后的原因硬爆。

推薦閱讀之前的文章
深入淺出Java(Android )線程池ThreadPoolExecutor

大家可以對著這個流程圖去學(xué)習(xí)源碼,把這個圖掌握了擎鸠,線程池原理也就差不多了


線程池工作流程

首先來看一下執(zhí)行線程任務(wù)的方法缀磕,里面很簡單,就是根據(jù)工作線程數(shù)量去執(zhí)行不同的策略劣光,里面分成了3種情況袜蚕,但是都會執(zhí)行addWoker()方法

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        /*
         * 如果當(dāng)前活躍線程數(shù)小于核心線程數(shù),就會添加一個worker來執(zhí)行任務(wù)绢涡;
         * 具體來說牲剃,新建一個核心線程放入線程池中,并把任務(wù)添加到該線程中垂寥。
         */
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }

      //程序執(zhí)行到這里颠黎,說明要么活躍線程數(shù)大于核心線程數(shù)另锋;要么addWorker()失敗

        /*
         * 如果當(dāng)前線程池是運(yùn)行狀態(tài),會把任務(wù)添加到隊列
         */
        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);
        }

       //程序執(zhí)行到這里狭归,說明要么線程狀態(tài)不是RUNNING夭坪;要么workQueue隊列已經(jīng)滿了

        //調(diào)用addWorker方法去創(chuàng)建非核心線程,
        //如果當(dāng)前線程數(shù)已經(jīng)達(dá)到 maximumPoolSize过椎,執(zhí)行拒絕策略
        else if (!addWorker(command, false))
            reject(command);
    }

這個方法就和名字一樣室梅,添加一個工人來完成任務(wù);而這個工人就是Thread疚宇,任務(wù)就是Runnable亡鼠。

private boolean addWorker(Runnable firstTask, boolean core) {
       ...省略一些不重要的

        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            //Worker是實現(xiàn)了Runnable接口的包裝類
            w = new Worker(firstTask);
            //Thread是在Worker構(gòu)造方法創(chuàng)建的
            final Thread t = w.thread;
            if (t != 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.
                    int rs = runStateOf(ctl.get());
                  
                    //檢查線程池狀態(tài),分為2種情況
                    //1敷待、線程池處于RUNNING
                   //2间涵、線程池處于SHUTDOWN并且firstTask==null
                   //這2種情況都會創(chuàng)建Worker來執(zhí)行隊列中的任務(wù)
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        workers.add(w);
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        //重新設(shè)置標(biāo)識位
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    //啟動線程
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

到這里,我們大概知道是通過創(chuàng)建Worker來執(zhí)行任務(wù)的榜揖,而且線程是在Worker內(nèi)部創(chuàng)建的勾哩,我們也能猜到Thread需要的Runnable應(yīng)該也在Worker內(nèi)部,所有我們繼續(xù)看一下Worker類举哟。

Worker

private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
     
        final Thread thread;
        /** Initial task to run.  Possibly null. */
        Runnable firstTask;
        /** Per-thread task counter */
        volatile long completedTasks;

        /**
         * Creates with given first task and thread from ThreadFactory.
         * @param firstTask the first task (null if none)
         */
        Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }

        /** Delegates main run loop to outer runWorker  */
        public void run() {
            runWorker(this);
        }

Worker居然是一個Runnable任務(wù)思劳,而且Worker的構(gòu)造方法中創(chuàng)建了Thread對象。這樣的話妨猩,在之前的addWorker()方法中調(diào)用t.start();就會執(zhí)行到Worker的run()方法潜叛。

繼續(xù)來看看runWorker()方法,這里很關(guān)鍵壶硅,一定要仔細(xì)看威兜。

final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        //這個就是addWorker傳進(jìn)來的Runnable
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            //如果task不為null或從workQueue中獲取任務(wù)不為null
            //就會一直執(zhí)行
            while (task != null || (task = getTask()) != null) {
                w.lock();
                // If pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt

                //檢查線程池狀態(tài),如果線程池處于中斷狀態(tài)森瘪,將調(diào)用interrupt將線程中斷牡属。 
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    //中斷線程
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        //線程任務(wù)執(zhí)行啦
                        task.run();
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }

這里的關(guān)鍵在于這個while()條件判斷票堵,當(dāng)?shù)谝淮蝿?chuàng)建Worker時就有任務(wù)扼睬,當(dāng)執(zhí)行完這個任務(wù)后,這個方法并沒有結(jié)束悴势,而是不斷地調(diào)用getTask()方法從阻塞隊列中獲取任務(wù)然后調(diào)用task.run()執(zhí)行任務(wù)窗宇。

getTask()獲取任務(wù)

private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);

            // 1.allowCoreThreadTimeOut表示是否允許核心線程超時銷毀,默認(rèn)是false,也就是說核心線程即使空閑也不會被銷毀
          //當(dāng)然特纤,如果設(shè)置為true军俊,核心線程是會銷毀的
          //這樣的話,只有正在工作的線程數(shù)大于核心線程數(shù)才會為true捧存,否則返回false
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
                //2.如果timed為true粪躬,通過poll取任務(wù)担败;如果為false,通過take取任務(wù)
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

這個方法是通過一個死循環(huán)取任務(wù)镰官,取任務(wù)的話是通過workQueue這個阻塞隊列去完成的提前,在默認(rèn)不改變allowCoreThreadTimeOut的前提下,如果工作線程數(shù)大于核心線程數(shù)泳唠,則通過poll()從隊列取任務(wù)狈网;否則通過take()從隊列取任務(wù);這2個方法的區(qū)別笨腥,

  • take():如果隊列中任務(wù)為空拓哺,會調(diào)用Condition.await()阻塞當(dāng)前線程。
  • poll(long timeout, TimeUnit unit):如果隊列中任務(wù)為空脖母,也會阻塞當(dāng)前線程士鸥,但是阻塞時長為timeout

    這樣的話,是不是已經(jīng)搞清楚線程池中的核心線程復(fù)用的原因了谆级。

線程的喚醒是在execute時础淤,當(dāng)調(diào)用workQueue.offer()方法,將任務(wù)放入阻塞隊列時哨苛,會調(diào)用Condition.signal()方法喚醒一個之前阻塞的線程鸽凶。這部分不細(xì)講,感興趣的同學(xué)自行查看建峭。

總結(jié)

  • 1玻侥、當(dāng)Thread的run方法執(zhí)行完一個任務(wù)之后,會循環(huán)地從阻塞隊列中取任務(wù)來執(zhí)行亿蒸,這樣執(zhí)行完一個任務(wù)之后就不會立即銷毀了凑兰;
  • 2、當(dāng)工作線程數(shù)小于核心線程數(shù)边锁,那些空閑的核心線程再去隊列取任務(wù)的時候姑食,如果隊列中的Runnable數(shù)量為0,就會阻塞當(dāng)前線程茅坛,這樣線程就不會回收了

感謝以下作者

Java線程池實現(xiàn)原理及其在美團(tuán)業(yè)務(wù)中的實踐
線程池原理
徹底理解Java線程池原理篇

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末音半,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子贡蓖,更是在濱河造成了極大的恐慌曹鸠,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件斥铺,死亡現(xiàn)場離奇詭異彻桃,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)晾蜘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進(jìn)店門邻眷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來眠屎,“玉大人,你說我怎么就攤上這事肆饶∽榱Γ” “怎么了?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵抖拴,是天一觀的道長燎字。 經(jīng)常有香客問我,道長阿宅,這世上最難降的妖魔是什么候衍? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮洒放,結(jié)果婚禮上蛉鹿,老公的妹妹穿的比我還像新娘。我一直安慰自己往湿,他們只是感情好妖异,可當(dāng)我...
    茶點故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著领追,像睡著了一般他膳。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上绒窑,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天棕孙,我揣著相機(jī)與錄音,去河邊找鬼些膨。 笑死蟀俊,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的订雾。 我是一名探鬼主播肢预,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼洼哎!你這毒婦竟也來了烫映?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤谱净,失蹤者是張志新(化名)和其女友劉穎窑邦,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體壕探,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年郊丛,在試婚紗的時候發(fā)現(xiàn)自己被綠了李请。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瞧筛。...
    茶點故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖导盅,靈堂內(nèi)的尸體忽然破棺而出较幌,到底是詐尸還是另有隱情,我是刑警寧澤白翻,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布乍炉,位于F島的核電站,受9級特大地震影響滤馍,放射性物質(zhì)發(fā)生泄漏岛琼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一巢株、第九天 我趴在偏房一處隱蔽的房頂上張望槐瑞。 院中可真熱鬧,春花似錦阁苞、人聲如沸困檩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽悼沿。三九已至,卻和暖如春骚灸,著一層夾襖步出監(jiān)牢的瞬間显沈,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工逢唤, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留拉讯,地道東北人。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓鳖藕,卻偏偏與公主長得像魔慷,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子著恩,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,955評論 2 355