線程池復(fù)用原理

更多精彩請關(guān)注公眾號xhJaver,京東工程師和你一起成長

一、線程池狀態(tài)

首先我們要明確線程池的幾種狀態(tài)

1. RUNNING

  • 這個狀態(tài)表明線程池處于正常狀態(tài),可以處理任務(wù),可以接受任務(wù)

2. SHUTDOWN

  • 這個狀態(tài)表明線程池處于正常關(guān)閉狀態(tài),不再接受任務(wù),但是可以處理線程池中剩余的任務(wù)

3. STOP

  • 這個狀態(tài)表明線程池處于停止?fàn)顟B(tài),不僅不會再接收新任務(wù),并且還會打斷正在執(zhí)行的任務(wù)

4. TIDYING

  • 這個狀態(tài)表明線程池已經(jīng)沒有了任務(wù),所有的任務(wù)都被停掉了

5. TERMINATED

  • 線程池徹底終止?fàn)顟B(tài)

他們的狀態(tài)轉(zhuǎn)換圖如下

線程池狀態(tài).png

<figcaption style="margin-top: 5px; text-align: center; color: #888; font-size: 14px;">線程池狀態(tài)</figcaption>

好了,知道了線程池的幾種狀態(tài)和他們是如何轉(zhuǎn)換的關(guān)系之后,我們來看一下 當(dāng)我們提交一個任務(wù)時,線程池到底發(fā)生了什么?!

我們平常使用線程池是這樣使用的

 for (int i=0;i<10;i++){
            //創(chuàng)建10個任務(wù)
            Task task = new Task("task" + i);
            //讓我們自定義的線程池去跑這些任務(wù)
            threadPoolExecutor.execute(task);
        }

我們來看一下 execute里面究竟有什么奇怪的東西?

二、execute源碼

  public void execute(Runnable command) {
        //1.先判斷提交的任務(wù)是不是空的
        if (command == null)
            throw new NullPointerException();
        //2.獲得線程池狀態(tài)
        int c = ctl.get();
        //3.判斷線程池數(shù)量是否小于核心線程數(shù)
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        //4.線程池數(shù)量大于等于核心線程數(shù)并且線程池處于Running狀態(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);
        }
        // 5.走到這里說明添加阻塞隊列失敗,
        // 創(chuàng)建非核心線程也失敗的話,執(zhí)行拒絕策略
        else if (!addWorker(command, false))
            reject(command);
    }

然后我們來看3那里,

 //3.判斷線程池數(shù)量是否小于核心線程數(shù)
        if (workerCountOf(c) < corePoolSize) {
            //如果小于核心線程數(shù)
            //3.1添加worker
            if (addWorker(command, true))
                //添加成功,返回
                return;
            //3.2添加失敗,獲取線程池狀態(tài)
            c = ctl.get();
        }

我用頭發(fā)想想都知道,線程復(fù)用的秘密肯定藏在了addworker里,哦對我沒有頭發(fā) 我們再來看一看他里面有什么鬼

三、addworker源碼

 private boolean addWorker(Runnable firstTask, boolean core) {
      //標(biāo)志位,一會兒會跳過來
        retry:
        for (;;) {
            //判斷線程池狀態(tài)
            int c = ctl.get();
            int rs = runStateOf(c);
            //如果狀態(tài)非法則返回false
            if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty()))
                return false;

            for (;;) {
                //判斷線程池線程總數(shù)量
                int wc = workerCountOf(c);
                //如果數(shù)量不符合要求則返回false
                if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                //線程池數(shù)量加1
                if (compareAndIncrementWorkerCount(c))
                    //跳到開始 retry處且往下執(zhí)行,不在進入大循環(huán)
                    break retry;
                //數(shù)量增加失敗的話判斷當(dāng)前線程池狀態(tài)若和剛才狀態(tài)不一致則繼續(xù)執(zhí)行大循環(huán)
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)
                    continue retry;
            }
        }

        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            //將提交的任務(wù)封裝進worker
            w = new Worker(firstTask);
            //得到worker中的線程
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    //得到線程池狀態(tài)
                    int rs = runStateOf(ctl.get());
                    //如果狀態(tài)合法
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive())
                            throw new IllegalThreadStateException();
                        //將worker添加至workers中 (這是個set集合,真正的線程池) 
                        workers.add(w);
                        //判斷線程數(shù)量
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        //添加worker成功
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    //執(zhí)行worker中的線程
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

其中很重要的一段代碼是

 //將提交的任務(wù)封裝進worker
            w = new Worker(firstTask);
            //得到worker中的線程
            final Thread t = w.thread;
            ....
            .....
              //執(zhí)行worker中的線程
                    t.start();

主要我們看這其中的worker是什么東西 (截取了worker中一部分源碼)

四、Worker源碼

private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {

        private static final long serialVersionUID = 6138294804551838833L;

        /** Thread this worker is running in.  Null if factory fails. */
        final Thread thread;
        /** Initial task to run.  Possibly null. */
        Runnable firstTask;
        /** Per-thread task counter */
        volatile long completedTasks;

        Worker(Runnable firstTask) {
            //線程池狀態(tài)設(shè)為running
            setState(-1); // inhibit interrupts until runWorker
            //用戶提交的任務(wù)
            this.firstTask = firstTask;
//通過創(chuàng)建一個線程,傳入的this是woker自身  worker繼承了Runnable 那么這個線程在t.start就是調(diào)用重寫的run()方法了
            this.thread = getThreadFactory().newThread(this);
        }

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

我們注意到剛才的t.start(); 就是執(zhí)行woker中的run方法,run方法又執(zhí)行了runworker() 方法 我們再來看下 runworker() 方法

五、runworker源碼

final void runWorker(Worker w) {
        //得到當(dāng)前線程
        Thread wt = Thread.currentThread();
        //這是我們提交的任務(wù)
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            //線程復(fù)用的密碼就在這里,是一個while循環(huán),判斷如果提交的任務(wù)不為空或者隊列里有任務(wù)的話
            while (task != null || (task = getTask()) != null) {
                w.lock();

                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    //執(zhí)行前的函數(shù),用戶可以自己拓展
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        //任務(wù)自己的run方法
                        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 {
                        //執(zhí)行后的函數(shù),用戶可以自己拓展
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            //銷毀線程
            processWorkerExit(w, completedAbruptly);
        }
    }

重點來了,我們來看一下 getTask()

六、getTask源碼

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

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

            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }
            //得到線程池線程數(shù)量 
            int wc = workerCountOf(c);
            // 是否設(shè)置超時時間  allowCoreThreadTimeOut默認(rèn)是false   
            //判斷線程池數(shù)量是否大于核心線程數(shù),如果大于的話  timed為true
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

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

            try {
                //這里 timed為ture的時候,采用帶超時時間的獲取元素的方法, 否則采取一直阻塞的方法
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                //獲取到任務(wù)就返回
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

由此可見 getTask里面是有超時標(biāo)志的timed的,我們在第一篇平常說的線程池原理里面講到,若非核心線程空閑keepAliveTime分鐘則銷毀,就是在這里,keepAliveTime時間內(nèi)未獲取到任務(wù),即為線程空閑狀態(tài),就退出了runWorker中的while循環(huán),進行銷毀線程的操作。

核心線程一定不會銷毀嗎? 我們注意到,這里面有一個allowCoreThreadTimeOut變量,如果他要是為true的話,那么核心線程也是可以銷毀的

threadPoolExecutor.allowCoreThreadTimeOut(true);

真的有核心線程與非核心線程之分嗎? 其實是沒有區(qū)別的,他們都是一樣的線程,線程池源碼中并沒有核心線程這個標(biāo)記,只是有一個核心線程數(shù)量,在這個數(shù)量之前創(chuàng)建先線程和在這個數(shù)量之后創(chuàng)建線程,默認(rèn)在這個數(shù)量之后創(chuàng)建的線程會在keepAliveTime空閑時間內(nèi)銷毀,我們?yōu)榱朔奖阌洃洠鴮⑵浞Q為非核心線程

七、總結(jié)

大體流程如下圖所示


線程池源碼流程圖.PNG

我們向線程池提交任務(wù)后,線程池會將我們的任務(wù)封裝成一個worker,這個worker里面有要執(zhí)行的線程t和要執(zhí)行的任務(wù),這個線程t的主要任務(wù)就是t.start,運行runworker方法,在runworker方法中,會一直while循環(huán)獲取提交的任務(wù)若沒有提交的任務(wù)則會看隊列里有沒有任務(wù),獲取隊列任務(wù)時就會判斷超時標(biāo)志是否為true,如果為true的話,則在超時時間內(nèi)未獲取到任務(wù)則返回null,然后銷毀當(dāng)前線程,否則一直等待到獲取到任務(wù)為止,不銷毀線程,這樣就做到了線程復(fù)用。

更多精彩請關(guān)注公眾號xhJaver,京東工程師和你一起成長

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,836評論 6 540
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,275評論 3 428
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,904評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,633評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,368評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,736評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,740評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,919評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,481評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,235評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,427評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,968評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,656評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,055評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,348評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,160評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,380評論 2 379