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

概述

現(xiàn)在機(jī)器基本都是多核的,開啟多線程可以有效地增加系統(tǒng)的吞吐量和性能旅挤,如下是開啟一個線程最簡單的方式

new Thread(new Runnable() {
    @Override
    public void run() {
        // do something
    }
}).start();

這個線程使用完后瓤逼,就會被系統(tǒng)所回收笼吟。線程雖是輕量級的,但其創(chuàng)建霸旗、關(guān)閉依然需要花費時間贷帮、資源。當(dāng)任務(wù)粒度不大的時候诱告,大量創(chuàng)建線程會得不償失撵枢。而且當(dāng)線程數(shù)超過核心數(shù),創(chuàng)建后的線程還會處于等待狀態(tài)精居。

因此線程的數(shù)量最好是能控制的锄禽,且能夠復(fù)用,線程池即能滿足需求靴姿。下面看看創(chuàng)建一個線程池沃但,并提交一個任務(wù)去執(zhí)行的方式

ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(new Runnable() {
    @Override
    public void run() {
        // do something
    }
});

實現(xiàn)原理

上面創(chuàng)建了一個只擁有一個線程的線程池,并提交一個任務(wù)去執(zhí)行佛吓。線程池的創(chuàng)建可以使用線程池工廠 Executors 里面的 new... 系列方法宵晚,含義如下

Executors.newSingleThreadExecutor(); // 創(chuàng)建一個只有單個線程的線程池垂攘。任務(wù)提交后,若這個線程空閑坝疼,則執(zhí)行搜贤,否則加入隊列,待線程空閑后執(zhí)行
Executors.newFixedThreadPool(numbersOfThread); // 創(chuàng)建一個有numbersOfThread個線程的線程池钝凶。任務(wù)提交后仪芒,若有空閑線程,則執(zhí)行耕陷,否則加入隊列掂名,待有線程空閑后執(zhí)行
Executors.newCachedThreadPool(); // 創(chuàng)建一個有無限個線程的線程池。任務(wù)提交后哟沫,若有空閑線程饺蔑,則執(zhí)行,否則創(chuàng)建新的線程去執(zhí)行
Executors.newSingleThreadScheduledExecutor(); // 在newSingleThreadExecutor之上擴(kuò)展了在給定時間執(zhí)行某任務(wù)的功能
...

查看線程工廠 Executors new... 方法的實現(xiàn)嗜诀,我們可以看到最終都是調(diào)用了 ThreadPoolExecutor 的構(gòu)造方法

public ThreadPoolExecutor(int corePoolSize, // 核心線程數(shù)量
                          int maximumPoolSize, // 最大線程數(shù)量
                          long keepAliveTime, // 當(dāng)線程數(shù)量超過核心線程數(shù)量時猾警,其余線程保活時間
                          TimeUnit unit, // 時間單位
                          BlockingQueue<Runnable> workQueue, // 任務(wù)隊列
                          ThreadFactory threadFactory, // 線程工廠
                          RejectedExecutionHandler handler // 任務(wù)提交失敗時的執(zhí)行策略
)

這里先說下任務(wù)提交后隆敢,線程池的執(zhí)行策略

(1) 當(dāng)線程池的實際線程數(shù)量小于corePoolSize時发皿,則優(yōu)先創(chuàng)建核心線程;
(2) 若大于等于corePooSize拂蝎,則將新的任務(wù)加入等待隊列穴墅;
(3) 若加入隊列失敗,并且線程數(shù)小于maximumPoolSize温自,則創(chuàng)建新的線程執(zhí)行任務(wù)玄货;
(4) 否則執(zhí)行拒絕策略。

下面我們看看具體的源碼

    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) { // workerCountOf:當(dāng)前線程總數(shù)
            if (addWorker(command, true)) // 創(chuàng)建一個核心線程并執(zhí)行當(dāng)前提交任務(wù)
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) { // 把任務(wù)加到任務(wù)隊列里面
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false)) // 創(chuàng)建一個非核心線程并執(zhí)行任務(wù)
            reject(command);
    }

接下來我們看看 Worker 工作線程的執(zhí)行過程

    private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable // 實現(xiàn)了Runnable接口
    {

        Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this); // 用一開始設(shè)置的線程工廠創(chuàng)建了線程悼泌,addWorker之后會調(diào)用這個線程的start方法啟動線程
        }

        public void run() {
            runWorker(this); // 執(zhí)行工作線程
        }

        final void runWorker(Worker w) {
            try {
                while (task != null || (task = getTask()) != null) { // 如果當(dāng)前任務(wù)未執(zhí)行松捉,則先執(zhí)行當(dāng)前任務(wù);否則去任務(wù)隊列里面拿任務(wù)執(zhí)行券躁,拿不到任務(wù)時惩坑,這個線程就退出了
                    ...
                    try {
                        beforeExecute(wt, task); //任務(wù)執(zhí)行前的回調(diào)
                        Throwable thrown = null;
                        try {
                            task.run(); // 任務(wù)執(zhí)行
                        } finally {
                            afterExecute(task, thrown); // 任務(wù)執(zhí)行完的回掉
                        }
                    } finally {
                        task = null;
                        w.completedTasks++;
                        w.unlock();
                    }
                }
                completedAbruptly = false;
            } finally {
                processWorkerExit(w, completedAbruptly);
            }
        }

接下來看看 getTask() 獲取任務(wù)的過程

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

        for (;;) {
            int wc = workerCountOf(c);

            // Are workers subject to culling?
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; // 要等待 or 阻塞

            try {
                Runnable r = timed ?
                    // 從隊列里面拿任務(wù),如果隊列為空也拜,最長等待時間為 keepAliveTime
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : 
                    // 從隊列里面拿任務(wù)以舒,如果隊列為空,則阻塞慢哈,直到隊列有新任務(wù)添加為止
                    workQueue.take(); 
                    // 所以線程池的核心線程為什么不會銷毀蔓钟、
                    // 非核心線程為什么能存活 keepAliveTime 時間,
                    // 超時后會被回收卵贱,是不是豁然開朗了滥沫?
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

可以看到侣集,線程池實現(xiàn)的相關(guān)特性,主要是通過 getTask() 時從任務(wù)隊列里面拿任務(wù)的等待阻塞來實現(xiàn)的

至于線程工廠兰绣,這里不再贅述世分。

下面最后再看看拒絕策略的種類

(1) AbortPolicy:直接拋異,阻止系統(tǒng)正常工作
(2) CallerRunsPolicy:只要線程池未關(guān)閉缀辩,直接在調(diào)用者線程執(zhí)行當(dāng)前任務(wù)
(3) DiscardOledesPolicy:丟棄最老的請求臭埋,嘗試重新提交任務(wù)
(4) DiscardPolicy:默默丟棄當(dāng)前提交的任務(wù)
或者可以自己實現(xiàn)RejectedExecutionHandler接口

至此,線程池的大體實現(xiàn)基本就清晰了

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末臀玄,一起剝皮案震驚了整個濱河市瓢阴,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌健无,老刑警劉巖荣恐,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異累贤,居然都是意外死亡叠穆,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門臼膏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來痹束,“玉大人,你說我怎么就攤上這事讶请。” “怎么了屎媳?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵夺溢,是天一觀的道長。 經(jīng)常有香客問我烛谊,道長风响,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任丹禀,我火速辦了婚禮状勤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘双泪。我一直安慰自己持搜,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布焙矛。 她就那樣靜靜地躺著葫盼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪村斟。 梳的紋絲不亂的頭發(fā)上贫导,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天抛猫,我揣著相機(jī)與錄音,去河邊找鬼孩灯。 笑死闺金,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的峰档。 我是一名探鬼主播败匹,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼面哥!你這毒婦竟也來了哎壳?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤尚卫,失蹤者是張志新(化名)和其女友劉穎归榕,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體吱涉,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡刹泄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了怎爵。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片特石。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖鳖链,靈堂內(nèi)的尸體忽然破棺而出姆蘸,到底是詐尸還是另有隱情,我是刑警寧澤芙委,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布逞敷,位于F島的核電站,受9級特大地震影響灌侣,放射性物質(zhì)發(fā)生泄漏推捐。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一侧啼、第九天 我趴在偏房一處隱蔽的房頂上張望牛柒。 院中可真熱鬧,春花似錦痊乾、人聲如沸皮壁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽闪彼。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間畏腕,已是汗流浹背缴川。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留描馅,地道東北人把夸。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像铭污,于是被迫代替她去往敵國和親恋日。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345

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