java源碼-ThreadPoolExecutor(3)

開篇

?部門以前有一個(gè)很跳的程序員寫了一個(gè)神奇的代碼猪落,通過不停創(chuàng)建ThreadPoolExecutor而不調(diào)用shutdown導(dǎo)致線程過多溢出囚衔,然后想想畢竟我還是一個(gè)不跳的程序員睛蛛,所以還是研究研究比較合適痴怨。這篇文章就是說明線程池的退出過程棵癣。
java源碼-ThreadPoolExecutor(1)
java源碼-ThreadPoolExecutor(2)
java源碼-ThreadPoolExecutor(3)


shutdown源碼解析

  • 1苞七、上鎖藐守,mainLock是線程池的主鎖,是可重入鎖蹂风,當(dāng)要操作workers set這個(gè)保持線程的HashSet時(shí)卢厂,需要先獲取mainLock,還有當(dāng)要處理largestPoolSize惠啄、completedTaskCount這類統(tǒng)計(jì)數(shù)據(jù)時(shí)需要先獲取mainLock
  • 2慎恒、判斷調(diào)用者是否有權(quán)限shutdown線程池
  • 3、使用CAS操作將線程池狀態(tài)設(shè)置為shutdown撵渡,shutdown之后將不再接收新任務(wù)
  • 4融柬、中斷所有空閑線程 interruptIdleWorkers()
  • 5、onShutdown()姥闭,ScheduledThreadPoolExecutor中實(shí)現(xiàn)了這個(gè)方法丹鸿,可以在shutdown()時(shí)做一些處理
  • 6、解鎖
  • 7棚品、嘗試終止線程池 tryTerminate()
    public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            // 權(quán)限檢查
            checkShutdownAccess();
            
            // 設(shè)置當(dāng)前線程池狀態(tài)為SHUTDOWN靠欢,如果已經(jīng)是SHUTDOWN則直接返回
            advanceRunState(SHUTDOWN);
            
            // 設(shè)置中斷標(biāo)志,中斷所有等待任務(wù)的空閑線程
            interruptIdleWorkers();

            // ThreadPoolExecutor沒什么操作
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        // 嘗試狀態(tài)變?yōu)門ERMINATED
        tryTerminate();
    }


    private void advanceRunState(int targetState) {
        for (;;) {
            int c = ctl.get();
            // 設(shè)置執(zhí)行狀態(tài)為SHUTDOWN
            if (runStateAtLeast(c, targetState) ||
                ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
                break;
        }
    }

    
    // onlyOne在這里傳入為false
    private void interruptIdleWorkers(boolean onlyOne) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            // 遍歷所有worker铜跑,通過判斷能否獲取鎖來判定worker是否空閑
            for (Worker w : workers) {
                Thread t = w.thread;
                if (!t.isInterrupted() && w.tryLock()) {
                    try {
                        // 針對(duì)能夠獲取到鎖的線程门怪,線程正阻塞在獲取任務(wù)的過程中
                        // 通過中斷線程然后迫使線程退出阻塞然后工作線程退出工作
                        // 然后工作線程就自然的被回收了
                        t.interrupt();
                    } catch (SecurityException ignore) {
                    } finally {
                        w.unlock();
                    }
                }
                if (onlyOne)
                    break;
            }
        } finally {
            mainLock.unlock();
        }
    }


shutdownNow源碼解析

?shutdownNow() 和 shutdown()的大體流程相似,差別是:

  • 1锅纺、將線程池更新為stop狀態(tài)
  • 2掷空、調(diào)用 interruptWorkers() 中斷所有線程,包括正在運(yùn)行的線程
  • 3、將workQueue中待處理的任務(wù)移到一個(gè)List中坦弟,并在方法最后返回护锤,說明shutdownNow()后不會(huì)再處理workQueue中的任務(wù)
    public List<Runnable> shutdownNow() {
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            // 權(quán)限檢查
            checkShutdownAccess();
            
            // 設(shè)置線程池狀態(tài)為stop
            advanceRunState(STOP);

            // 中斷所有線程
            interruptWorkers();

            // 移除所有待消費(fèi)的任務(wù)
            tasks = drainQueue();
        } finally {
            mainLock.unlock();
        }

        // 嘗試狀態(tài)變?yōu)門ERMINATED
        tryTerminate();

        return tasks;
    }


awaitTermination源碼解析

?等待線程池狀態(tài)變?yōu)門ERMINATED則返回,或者時(shí)間超時(shí)酿傍。由于整個(gè)過程獨(dú)占鎖烙懦,所以一般調(diào)用shutdown或者shutdownNow后使用。

  • 等待過程中如果發(fā)現(xiàn)未超時(shí)那么通過for循環(huán)繼續(xù)等待超時(shí)
    public boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (;;) {
                if (runStateAtLeast(ctl.get(), TERMINATED))
                    return true;
                if (nanos <= 0)
                    return false;
                nanos = termination.awaitNanos(nanos);
            }
        } finally {
            mainLock.unlock();
        }
    }


工作線程Worker退出邏輯

?如果獲取task為null就會(huì)退出while循環(huán)從而執(zhí)行finally部分的邏輯赤炒,也就是processWorkerExit()方法執(zhí)行清了工作.

    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            // 如果獲取任務(wù)為null氯析,那么就會(huì)退出當(dāng)前工作線程
            while (task != null || (task = getTask()) != null) {
                w.lock();
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        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 {
            // 執(zhí)行清了工作
            processWorkerExit(w, completedAbruptly);
        }
    }

?getTask()方法(rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty()))當(dāng)中我們?cè)O(shè)置狀態(tài)為SHUTDOWN,同時(shí)workQueue為空的情況下就會(huì)返回null莺褒,在外層循環(huán)中就會(huì)退出線程的工作掩缓,實(shí)現(xiàn)線程退出。

    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);

            // Are workers subject to culling?
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

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

            try {
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }


參考文章

Java中線程池ThreadPoolExecutor原理探究
Java線程池ThreadPoolExecutor使用和分析(三) - 終止線程池原理

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末遵岩,一起剝皮案震驚了整個(gè)濱河市你辣,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌旷余,老刑警劉巖绢记,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異正卧,居然都是意外死亡蠢熄,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門炉旷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來签孔,“玉大人,你說我怎么就攤上這事窘行〖⒆罚” “怎么了?”我有些...
    開封第一講書人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵罐盔,是天一觀的道長(zhǎng)但绕。 經(jīng)常有香客問我,道長(zhǎng)惶看,這世上最難降的妖魔是什么捏顺? 我笑而不...
    開封第一講書人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮纬黎,結(jié)果婚禮上幅骄,老公的妹妹穿的比我還像新娘。我一直安慰自己本今,他們只是感情好拆座,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開白布主巍。 她就那樣靜靜地躺著,像睡著了一般挪凑。 火紅的嫁衣襯著肌膚如雪孕索。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,441評(píng)論 1 310
  • 那天躏碳,我揣著相機(jī)與錄音檬果,去河邊找鬼。 笑死唐断,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的杭抠。 我是一名探鬼主播脸甘,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼偏灿!你這毒婦竟也來了丹诀?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤翁垂,失蹤者是張志新(化名)和其女友劉穎铆遭,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體沿猜,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡枚荣,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了啼肩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片橄妆。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖祈坠,靈堂內(nèi)的尸體忽然破棺而出害碾,到底是詐尸還是另有隱情,我是刑警寧澤赦拘,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布慌随,位于F島的核電站,受9級(jí)特大地震影響躺同,放射性物質(zhì)發(fā)生泄漏阁猜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一笋籽、第九天 我趴在偏房一處隱蔽的房頂上張望蹦漠。 院中可真熱鬧,春花似錦车海、人聲如沸笛园。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)研铆。三九已至埋同,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間棵红,已是汗流浹背凶赁。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留逆甜,地道東北人虱肄。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像交煞,于是被迫代替她去往敵國(guó)和親咏窿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359