你了解線程池嗎

前言

如果有人問我:“你了解Java線程池嗎”,我不打算回答Java中常用的幾種線程池僚焦,也記不住。從線程池的上層API來看曙痘,再多種的線程池芳悲,無非是參數(shù)的不同立肘,讓它們呈現(xiàn)出了不同的特性,那么這些特性到底依賴什么樣的原理實現(xiàn)名扛,就更值得去深究谅年,也是本文的目的。

試著回答以下幾個問題:

  • 線程池如何實現(xiàn)
  • 非核心線程延遲死亡肮韧,如何做到
  • 核心線程為什么不會死
  • 如何釋放核心線程
  • 非核心線程能成為核心線程嗎
  • Runnable在線程池里如何執(zhí)行
  • 線程數(shù)如何做選擇
  • 常見的不同類型的線程池的功效如何做到

如果以上問題回答不出一二三融蹂,可以借鑒本文。

基礎知識

要了解線程池弄企,必然涉及到ThreadPoolExecutor超燃。ThreadPoolExecutors實現(xiàn)了線程池所需的最小功能集,已能hold住很多場景拘领。常見的線程池類型意乓,通過Executors提供的API,屏蔽了構造參數(shù)細節(jié)來創(chuàng)建ThreadPoolExecutors院究,因為不了解具體參數(shù)含義的話洽瞬,可能拿到的線程池與設想的會有偏差。

構造參數(shù)與對象成員變量

  • corePoolSize:核心線程數(shù)业汰,期望保持的并發(fā)狀態(tài)
  • maximumPoolSize:最大線程數(shù)伙窃,允許超載,雖然期望將并發(fā)狀態(tài)保持在一定范圍样漆,但是在任務過多時为障,增加非核心線程來處理任務。非核心線程數(shù) = maximumPoolSize - corePoolSize
  • workQueue:阻塞隊列放祟,存儲線程任務Runnable
  • keepAliveTime:在沒有任務時鳍怨,線程存活時間
  • threadFactory:用來構建線程
  • handler:當任務已滿,并且無法再增加線程數(shù)時跪妥,或拒絕添加任務時侦香,所執(zhí)行的策略

Worker

線程池中的工作線程以Worker作為體現(xiàn),真正工作的線程為Worker的成員變量顷锰,Worker即是Runnable,又是同步器贫堰。Worker從工作隊列中取出任務來執(zhí)行熄云,并能通過Worker控制任務狀態(tài)。

ctl

ctl用來控制線程池的狀態(tài)晴及,并用來表示線程池線程數(shù)量及皂。在線程池中,有以下五種狀態(tài)

  • RUNNABLE:運行狀態(tài)且改,接受新任務验烧,持續(xù)處理任務隊列里的任務
  • SHUTDOWN:不再接受新任務又跛,但要處理任務隊列里的任務
  • STOP:不接受新任務慨蓝,不再處理任務隊列里的任務,中斷正在進行中的任務
  • TIDYING:表示線程池正在停止運作礼烈,中止所有任務弧满,銷毀所有工作線程
  • TERMINATED:表示線程池已停止運作,所有工作線程已被銷毀济丘,所有任務已被清空或執(zhí)行完畢

狀態(tài)轉(zhuǎn)換關系如下圖


狀態(tài)轉(zhuǎn)換.png

ctl類型為AtomicInteger谱秽,那用一個基礎如何表示以上五種狀態(tài)以及線程池工作線程數(shù)量呢?int型變量占用4字節(jié)摹迷,共32位疟赊,因此采用位表示,可以解決上述問題峡碉。5種狀態(tài)使用5種數(shù)值進行表示近哟,需要占用3位,余下的29位就可以用來表示線程數(shù)鲫寄。因此吉执,高三位表示進程狀態(tài),低29位為線程數(shù)量地来,代碼如下:

    // 值為29
    private static final int COUNT_BITS = Integer.SIZE - 3;
    // 高三位全為0戳玫,低29位全為1,因此線程數(shù)量的表示范圍為 0 ~ 2^29
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

    /**
    因為ctl分位來表示狀態(tài)和數(shù)量未斑,下面幾個狀態(tài)僅看有效位的值
    */
    // 有效值為 111
    private static final int RUNNING    = -1 << COUNT_BITS;
    // 有效值為 000
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    // 有效值為 001
    private static final int STOP       =  1 << COUNT_BITS;
    // 有效值為 010
    private static final int TIDYING    =  2 << COUNT_BITS;
    // 有效值為 011
    private static final int TERMINATED =  3 << COUNT_BITS;
    
    // 默認狀態(tài)為RUNNING咕宿,線程數(shù)量為0
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

既然采用了int分位表示線程池狀態(tài)和線程數(shù)量,那么線程池自然提供了方法來獲取狀態(tài)與數(shù)量

  • runStateOf(): 獲取線程池狀態(tài)
  • workerCountOf(): 獲取工作線程數(shù)量

兩函數(shù)均為二進制操作,代碼不貼府阀,可用下圖說明:


ctl結構與操作.png

線程池實現(xiàn)

添加任務

線程池可以通過submit()缆镣、execute()提交線程任務,其中试浙,submit()可以通過Future拿到執(zhí)行結果董瞻,內(nèi)部也是通過execute()向線程池提交線程任務.

    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        // 獲取當前ctl值
        int c = ctl.get();
        // 當前線程數(shù)少于最大核心線程數(shù)
        if (workerCountOf(c) < corePoolSize) {
            // 添加核心線程,添加線程任務
            if (addWorker(command, true))
                return;
            // 上面的過程期間田巴,ctl可能已被更改钠糊,獲取最新值
            c = ctl.get();
        }
        // 線程池狀態(tài)為RUNNABLE,向工作隊列添加任務
        if (isRunning(c) && workQueue.offer(command)) {
            // 再次檢查用
            int recheck = ctl.get();
            // 線程不處于RUNNABLE狀態(tài)固额,移除任務
            if (! isRunning(recheck) && remove(command))
                // 執(zhí)行拒絕任務策略
                reject(command);
            else if (workerCountOf(recheck) == 0)
                // 執(zhí)行到這里說明已沒有可用的工作線程眠蚂,創(chuàng)建新的工作現(xiàn)線程
                // ,并從任務隊列里取任務斗躏。因為在這個時刻逝慧,存在所有工作線程
                // 都被釋放的可能,為了應對這個線程池“假死”的情況啄糙,所以創(chuàng)建
                // 了新的工作線程
                addWorker(null, false);
        }
        // 添加非核心隊列來執(zhí)行線程任務
        else if (!addWorker(command, false))
            // 說明線程池達到飽和笛臣,或者線程池shut down,執(zhí)行拒絕策略
            reject(command);
    }

當有任務到來時隧饼,按照如下策略進行:

  1. 如果當前核心線程數(shù)量沒達到最大值corePoolSize沈堡,創(chuàng)建新線程來執(zhí)行此任務
  2. 如果當前核心線程到達最大,向阻塞隊列添加任務
  3. 如果核心線程已滿燕雁,阻塞隊列已滿诞丽,嘗試開啟非核心線程來執(zhí)行任務
  4. 如果線程池不處于RUNNABLE狀態(tài),或者處于飽和狀態(tài)拐格,執(zhí)行任務拒絕策略

線程池是按照上面123的順序來處理新進的任務的僧免,并且在每一個過程中,會檢查ctl的最新值有效性捏浊,因為在處理過程中線程池的各種狀態(tài)隨時可能發(fā)生了改變懂衩。

不過是通過添加核心或是通過添加非核心線程來執(zhí)行任務,都是通過addWorker()來完成金踪,下面是代碼

   private boolean addWorker(Runnable firstTask, boolean core) {
        // 這個是類似 goto 的語法浊洞,代碼有效片段是下面第一for循環(huán)
        retry:
        for (;;) {
            int c = ctl.get();
            // 獲取程序狀態(tài)
            int rs = runStateOf(c);

            /**
             這一個條件需要仔細理解。
             1. 當線程處于STOP胡岔、TIDYING法希、TERMINATED時,線程池是拒絕執(zhí)行任務的
             因此不需要任務靶瘸,也不添加線程
             2. 當線程處于SHUTDOWN狀態(tài)時铁材,線程池需要把任務處理完尖淘,才會到達后面的
             TIDYING、TERMINATED狀態(tài)著觉。因此,如果阻塞隊列還有任務的話惊暴,繼續(xù)添加
             線程來加快處理饼丘。
            */
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            for (;;) {
                // 獲取線程數(shù)
                int wc = workerCountOf(c);
                // 線程數(shù)超過或等于能表示的上限
                // 或 比較 核心線程數(shù)達到上限,或比較線程池允許的最大線程數(shù)辽话,取決于core
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                // CAS操作增加線程數(shù)肄鸽,跳出循環(huán)
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();  // Re-read ctl
                // 上面的CAS操作沒成功,檢查線程池狀態(tài)與開始是否一致油啤,
                // 如果一致典徘,繼續(xù)執(zhí)行此for循環(huán),否則重新執(zhí)行retry代碼塊益咬,
                // 自旋以期CAS成功逮诲,后續(xù)才能添加線程
                if (runStateOf(c) != rs)
                    continue retry;
            }
        }

        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            // 將線程任務加入Worker,新增了Worker幽告,就是新增了線程
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    int rs = runStateOf(ctl.get());

                    // 再次檢查線程池狀態(tài)
                    // 1. 處于RUNNABLE狀態(tài)梅鹦,繼續(xù)添加線程執(zhí)行任務
                    // 2. 處于SHUTDOWN狀態(tài),到這里說明隊列里還有任務要執(zhí)行
                    // 增加線程期望讓任務執(zhí)行快一點
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        // 這里說明發(fā)生了意外狀況冗锁,新建的線程不可用
                        if (t.isAlive()) 
                            throw new IllegalThreadStateException();
                        // 添加worker進集合
                        workers.add(w);
                        int s = workers.size();
                        // largestPoolSize可以表示線程池達到的最大并發(fā)
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        // 添加線程成功    
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    // 啟動新添加的線程
                    t.start();
                    // 線程啟動成功
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                // 線程啟動失敗齐唆,移除work,銷毀線程
                addWorkerFailed(w);
        }
        return workerStarted;
    }

以上代碼做了如下幾件事:

  1. 線程池處于 RUNNBALE 或者處于 SHUTDOWN 并在阻塞隊列里還有任務時冻河,需要添加新線程箍邮。自旋確保 CAS 成功,然后添加新線程
  2. 線程存于Worker叨叙,線程池存有Worker信息锭弊,就能訪問線程
  3. 線程啟動失敗,則移除Worker摔敛,銷毀線程

addWorkerFailed()操作就不進去看了廷蓉,首先是將Worker移除,然后通過CAS操作更新ctl马昙,最后調(diào)用tryTerminate()操作嘗試中止線程池桃犬。

執(zhí)行任務

之前的代碼開啟了新線程并讓線程執(zhí)行,但是沒有看到有Runnable提交行楞。之前說過Worker本身為Runnable攒暇,并且存有為Thread類型的成員變量。線程池執(zhí)行的任務的線程子房,也就是Workder里的Thread形用。

    private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
        Worker(Runnable firstTask) {
            setState(-1);
            // firstTask就是addWorker()帶來的Runnable
            this.firstTask = firstTask;
            // 通過ThreadFactory創(chuàng)建線程就轧,將自己作為Runnable提交
            this.thread = getThreadFactory().newThread(this);
        }
        ......
    }

因此線程執(zhí)行后,執(zhí)行的是Worker.run()田度,run()則調(diào)用了ThreadPoolExecutor.runWorker()

   final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock();
        boolean completedAbruptly = true;
        try {
            // task一開始是firstTask妒御, 后面就通過getTask()從阻塞隊列里拿任務
            while (task != null || (task = getTask()) != null) {
                w.lock();
                // 線程池狀態(tài)檢查
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    // 這個方法子類可以重寫,在任務執(zhí)行前有回調(diào)
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        // 執(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 {
                        // 這個方法子類可以重寫镇饺,在任務執(zhí)行后有回調(diào)
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            // 線程池已沒有任務了乎莉,工作線程達到了可退出的狀態(tài),Worker退出
            processWorkerExit(w, completedAbruptly);
        }
    }

線程首個任務為firstTask奸笤,之后通過getTask()就從阻塞隊列里任務惋啃。線程池提供了beforeExecute()和afterExecute()通知子類任務執(zhí)行前后的回調(diào),讓子類有時機能執(zhí)行自己的事情监右。如果線程池已沒有任務了边灭,工作線程達到了可退出的狀態(tài),則將線程退出健盒。

主要看getTask() 和 processWorkerExit()

    private Runnable getTask() {
        // 超時標志
        boolean timedOut = false; 

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

            // 檢查線程池和阻塞隊列狀態(tài)
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                // 減少線程數(shù)
                decrementWorkerCount();
                return null;
            }
            
            // 獲取線程數(shù)
            int wc = workerCountOf(c);

            // 線程等待方式標志位判斷依據(jù)
            // allowCoreThreadTimeOut代表核心線程是不是能退出绒瘦,如果核心線程能退出,就更別說非核心線程了
            // 另一個則是看是否存在非核心線程
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
            
            // 超時味榛,或者并且線程超標超標椭坚,返回null,讓上一層函數(shù)退出線程
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
                // 如果timed為true搏色,則使用poll等待最多keepAliveTime時間獲取任務
                // 如果timed為false善茎,使用take()獲取任務,阻塞線程频轿,直到可以從阻塞隊列拿到任務
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                // 超時
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

線程池里的線程從阻塞隊列里拿任務垂涯,如果存在非核心線程,假設阻塞隊列里沒有任務航邢,那么非核心線程也要在等到keepAliveTime時間后才會釋放耕赘。如果當前僅有核心線程存在,如果允許釋放核心線程的話膳殷,也就和非核線程的處理方式一樣操骡,反之,則通過take()一直阻塞直到拿到任務赚窃,這也就是線程池里的核心線程為什么不死的原因册招。

從之前的代碼一直看到這,并沒有發(fā)現(xiàn)有明顯的標志來標志核心線程與非核心線程勒极,而是以線程數(shù)來表達線程身份是掰。0 ~ corePoolSize 表示線程池里只有核心線程,corePoolSize ~ maximumPoolSize 表示線程池里核心線程滿辱匿,存在非核心線程键痛。然后炫彩,根據(jù)區(qū)間狀態(tài)做有差異的處理⌒醵蹋可以大膽猜測江兢,線程池實際并不區(qū)分核心線程與非核心線程,是根據(jù)當前的總體并發(fā)狀態(tài)來決定怎樣處理線程任務丁频。corePoolSize是線程池希望達到并保持的并發(fā)狀態(tài)划址,而corePoolSize ~ maximumPoolSize則是線程池允許的并發(fā)的超載狀態(tài),不希望長期保持限府。

釋放線程

在線程沒有拿到任務后,退出線程痢缎,通過processWorkerExit()可以證實上述所言胁勺。

    private void processWorkerExit(Worker w, boolean completedAbruptly) {
        // 到這里說明線程中斷,先通過decrementWorkerCount()減少線程數(shù)值
        // 否則独旷,說明是線程沒有從阻塞隊列獲取到線程
        if (completedAbruptly) 
                decrementWorkerCount();

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            // completedTaskCount記錄線程池總共完成的任務
            // w.completedTasks則是線程完成的任務數(shù)
            completedTaskCount += w.completedTasks;
            // 移除Worker
            workers.remove(w);
        } finally {
            mainLock.unlock();
        }
        
        // 線程池狀態(tài)改變署穗,嘗試中止線程池
        tryTerminate();

        int c = ctl.get();
        // 檢查線程池狀態(tài),線程池處于RUNNABLE或者SHUTDOWN則進入
        if (runStateLessThan(c, STOP)) {
            if (!completedAbruptly) {
                // 線程池最小數(shù)量嵌洼,取決于是否能釋放核心線程
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                // 如果任務隊列還有線程案疲,最起碼都要有一個線程來處理任務
                if (min == 0 && ! workQueue.isEmpty())
                    min = 1;
                if (workerCountOf(c) >= min)
                    return; 
            }
            // 因為線程中斷,可能導致沒有線程來執(zhí)行阻塞隊列里的任務
            // 因此嘗試創(chuàng)建線程去執(zhí)行任務
            addWorker(null, false);
        }
    }

釋放工作線程也并沒有區(qū)分核心與非核心麻养,也是隨機進行的褐啡。所謂隨機,就是在前面所說的區(qū)間范圍內(nèi)鳖昌,根據(jù)釋放策略备畦,哪個線程先達到獲取不到任務的狀態(tài),就釋放哪個線程许昨。

文中多次出現(xiàn)tryTerminate()懂盐,但不深入去看了。里邊最主要的操作是糕档,發(fā)現(xiàn)可以中止線程池時莉恼,中止,并調(diào)用terminated()進行通知速那。如果線程池處于RUNNABLE狀態(tài)俐银,什么也不做,否則嘗試中斷一個線程琅坡。 中斷線程則是通過interruptIdleWorker()操作悉患,就不展開了。

到這里就能能明白線程池的原理的榆俺,如下圖


線程池工作原理(水滴篇).png

線程池里有容納一定的Worker售躁,Worker中的線程就是線程池中用來執(zhí)行任務的線程坞淮。當有任務加入線程時,根據(jù)線程池狀態(tài)的不同陪捷,有不同的步驟回窘。當核心線程未滿時,創(chuàng)建新線程來執(zhí)行市袖;否則將任務加入到阻塞隊列啡直;否則創(chuàng)建非核心線程來執(zhí)行。而線程獲取任務的方式有兩種苍碟,根據(jù)線程池容量區(qū)間酒觅,以及是否可以釋放核心線程來使用take()或者poll()來獲取任務,其中poll()在一定時間內(nèi)獲取不到任務微峰,則當前線程會被釋放舷丹。

當然,在addWorker()方法來有任務添加失敗的策略蜓肆,也就是RejectedExecutionHandler颜凯。ThreadPoolExecutor實現(xiàn)了四種策略來進行處理,簡單了解即可:

  • CallerRunsPolicy: 如果線程池沒有SHUTODOWN的話仗扬,直接執(zhí)行任務
  • AbortPolicy: 拋出異常症概,說明當前情況的線程池不希望得到接收不了任務的狀態(tài)
  • DiscardOldestPolicy: 丟棄阻塞隊列最舊的任務
  • DiscardPolicy: 什么也不做

需要注意的是,默認情況下策略為AbortPolicy早芭。

總結

做個總結:

  1. 線程池傾向于使用核心線程來處理任務彼城,從任務的添加策略可以看出,先考慮創(chuàng)建核心線程處理逼友,再考慮放到阻塞隊列精肃,再考慮創(chuàng)建非核心線程處理。以上都不行帜乞,則使用任務拒絕策略
  2. 通過向阻塞隊列取任務的不同操作司抱,能確保線程的存活,take()保證核心線程不死黎烈,poll()保證非核心線程存活等待一定時間
  3. 線程池不區(qū)分核心線程和非核心線程习柠,線程池是期望達到corePoolSize的并發(fā)狀態(tài),并允許在不得已情況下超載照棋,達到corePoolSize ~ maximumPoolSize 的并發(fā)狀態(tài)
  4. 線程池狀態(tài)和線程數(shù)量用ctl表示资溃,高三位為狀態(tài),低29位為當前線程池數(shù)量
  5. 線程池對狀態(tài)的檢測非沉姨浚苛刻溶锭,幾乎在所有稍微耗時或影響下一步操作正確性的代碼前都校驗ctl

線程池中有很多值得學習的東西,線程容量調(diào)整的設計符隙、ctl的設計趴捅、任務調(diào)度的設計等垫毙。也有需要更深的儲備才能看懂的實現(xiàn),這里點出拱绑,以備近一步學習综芥,如同步器的使用,并發(fā)場景的考慮與應用等猎拨。

下面回答開篇提出的問題膀藐。

問答

線程池如何實現(xiàn)
總結就是這個問題的答案

非核心線程延遲死亡,如何實現(xiàn)
通過阻塞隊列poll()红省,讓線程阻塞等待一段時間额各,如果沒有取到任務,則線程死亡

核心線程為什么不死
通過阻塞隊列take()吧恃,讓線程一直等待臊泰,直到獲取到任務

如何釋放核心線程
將allowCoreThreadTimeOut設置為true⊙潦啵可用下面代碼實驗

// 偽代碼
{
    // 允許釋放核心線程,等待時間為100毫秒
    es.allowCoreThreadTimeOut(true);
    for(......){
        // 向線程池里添加任務针饥,任務內(nèi)容為打印當前線程池線程數(shù)
        Thread.currentThread().sleep(200);
    }
}

線程數(shù)會一直為1厂抽。 如果allowCoreThreadTimeOut為false,線程數(shù)會逐漸達到飽和丁眼,然后大家一起阻塞等待筷凤。

非核心線程能成為核心線程嗎
線程池不區(qū)分核心線程于非核心線程,只是根據(jù)當前線程池容量狀態(tài)做不同的處理來進行調(diào)整苞七,因此看起來像是有核心線程于非核心線程藐守,實際上是滿足線程池期望達到的并發(fā)狀態(tài)。

Runnable在線程池里如何執(zhí)行
線程執(zhí)行Worker蹂风,Worker不斷從阻塞隊列里獲取任務來執(zhí)行卢厂。如果任務加入線程池失敗,則在拒絕策略里惠啄,還有處理機會慎恒。

線程數(shù)如何做選擇
這就要看任務類型是計算密集型任務還是IO密集型任務了,區(qū)別在于CPU占用率撵渡。計算密集型任務涉及內(nèi)存數(shù)據(jù)的存取融柬,CPU處于忙綠狀態(tài),因此并發(fā)數(shù)相應要低一些趋距。而IO密集型任務粒氧,因為外部設備速度不匹配問題,CPU更多是處于等待狀態(tài)节腐,因此可以把時間片分給其他線程外盯,因此并發(fā)數(shù)可以高一些摘盆。

常見的不同類型的線程池的功效如何做到
常見的線程池有:

  • CachedThreadPool:適合異步任務多,但周期短的場景
  • FixedThreadPool: 適合有一定異步任務门怪,周期較長的場景骡澈,能達到有效的并發(fā)狀態(tài)
  • SingleThreadExecutor: 適合任務串行的場景
  • ScheduledThreadPool: 適合周期性執(zhí)行任務的場景

對于如何選擇線程池就要看具體的場景,其中的差異通過構造參數(shù)可以到達效果掷空,通過之前的分析肋殴,就能知道參數(shù)的具體作用以及為什么能達到效果。取FixedThreadPool來看坦弟,拋磚引玉护锤。

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

nThreads個數(shù)量核心線程持續(xù)并發(fā)任務,沒有非核心線程酿傍,如果沒有任務烙懦,則通過take()阻塞等待,不允許核心線程死亡赤炒。并且阻塞隊列為LinkedBlockingQueue氯析,容量為Integer.MAX_VALUE,可以視為無界隊列莺褒,更難走到拒絕添加線程邏輯掩缓。

參考

線程池原理
徹底理解Java線程池原理篇
Java線程池---ThreadPoolExecutor中的ctl變量
JUC鎖框架_AbstractQueuedSynchronizer詳細分析

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市遵岩,隨后出現(xiàn)的幾起案子你辣,更是在濱河造成了極大的恐慌,老刑警劉巖尘执,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件舍哄,死亡現(xiàn)場離奇詭異,居然都是意外死亡誊锭,警方通過查閱死者的電腦和手機表悬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來丧靡,“玉大人签孔,你說我怎么就攤上這事【叫校” “怎么了饥追?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長罐盔。 經(jīng)常有香客問我但绕,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任捏顺,我火速辦了婚禮六孵,結果婚禮上,老公的妹妹穿的比我還像新娘幅骄。我一直安慰自己劫窒,他們只是感情好,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布拆座。 她就那樣靜靜地躺著主巍,像睡著了一般。 火紅的嫁衣襯著肌膚如雪挪凑。 梳的紋絲不亂的頭發(fā)上孕索,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機與錄音躏碳,去河邊找鬼搞旭。 笑死,一個胖子當著我的面吹牛菇绵,可吹牛的內(nèi)容都是我干的肄渗。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼咬最,長吁一口氣:“原來是場噩夢啊……” “哼恳啥!你這毒婦竟也來了?” 一聲冷哼從身側響起丹诀,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎翁垂,沒想到半個月后铆遭,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡沿猜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年枚荣,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片啼肩。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡橄妆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出祈坠,到底是詐尸還是另有隱情害碾,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布赦拘,位于F島的核電站慌随,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜阁猜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一丸逸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧剃袍,春花似錦黄刚、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至研铆,卻和暖如春埋同,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背棵红。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工凶赁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人逆甜。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓虱肄,卻偏偏與公主長得像,于是被迫代替她去往敵國和親交煞。 傳聞我的和親對象是個殘疾皇子咏窿,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

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