【并發(fā)編程系列12】從Java線程池的常用4種寫法深入分析線程池(Thread Pool)的實現(xiàn)原理

寫在前面的話

并發(fā)編程里面,線程池這個一直就想寫一篇文章來總結下,但是直到并發(fā)編程系列的第12篇才寫的原因是線程池里面用到了AQS同步隊列和阻塞隊列等一些知識,所以為了鋪墊,就先把前面的知識點寫完了,到現(xiàn)在,終于可以總結一下線程池的實現(xiàn)原理了。

什么是線程池

在Java中,創(chuàng)建一個線程可以通過繼承Thread或者實現(xiàn)Runnable接口來實現(xiàn),但是,如果每個請求都創(chuàng)建一個新線程,那么創(chuàng)建和銷毀線程花費的時間和消耗的系統(tǒng)資源都相當大,甚至可能要比在處理實際的用戶請求的時間和資源要多的多。

為了解決這個問題,就有了線程池的概念,線程池的核心邏輯是提前創(chuàng)建好若干個線程放在一個容器中。如果有任務需要處理,則將任務直接分配給線程池中的線程來執(zhí)行就行,任務處理完以后這個線程不會被銷毀,而是等待后續(xù)分配任務。同時通過線程池來重復管理線程還可以避免創(chuàng)建大量線程增加開銷。

創(chuàng)建線程池

為了方便使用,Java中的Executors類里面提供了幾個線程池的工廠方法,可以直接利用提供的方法創(chuàng)建不同類型的線程池:

  • newFixedThreadPool:創(chuàng)建一個固定線程數(shù)的線程池
  • newSingleThreadExecutor:創(chuàng)建只有1個線程的線程池
  • newCachedThreadPool:返回一個可根據(jù)實際情況調整線程個數(shù)的線程池,不限制最大線程 數(shù)量,若用空閑的線程則執(zhí)行任務,若無任務則不創(chuàng)建線程。并且每一個空閑線程會在60秒 后自動回收。
  • newScheduledThreadPool: 創(chuàng)建一個可以指定線程的數(shù)量的線程池,但是這個線程池還帶有 延遲和周期性執(zhí)行任務的功能,類似定時器。

FixedThreadPool

創(chuàng)建一個固定數(shù)量N個線程在一個共享的無邊界隊列上操作的線程池。在任何時候,最多N個線程被激活處理任務。如果所有線程都在活動狀態(tài)時又有新的任務被提交,那么新提交的任務會加入隊列等待直到有線程可用為止。

如果有任何線程在shutdown前因為失敗而被終止,那么當有新的任務需要執(zhí)行時會產(chǎn)生一個新的線程,新的線程將會一直存在線程池中,直到被顯式的shutdown。

示例

package com.zwx.concurrent.threadPool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestThreadPool {
    public static void main(String[] args) {
        //FixedThreadPool - 固定線程數(shù)
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
        for (int i=0;i<10;i++){
            fixedThreadPool.execute(()-> {
                System.out.println("線程名:" + Thread.currentThread().getName());
            });
        }
        fixedThreadPool.shutdown();
    }
}

輸出結果為:


在這里插入圖片描述

可以看到,最多只有3個線程在循環(huán)執(zhí)行任務(運行結果是不一定的,但是最多只會有3個線程)。

FixedThreadPool調用了如下方法構造線程池:


在這里插入圖片描述

SingleThreadExecutor

只有一個工作線程的執(zhí)行器。如果這個線程在正常關閉前因為執(zhí)行失敗而被關閉,那么就會重新創(chuàng)建一個新的線程加入執(zhí)行器。

這種執(zhí)行器可以保證所有的任務按順序執(zhí)行,并且在任何給定的時間內,確保活動的任務只有1個。

示例

package com.zwx.concurrent.threadPool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestThreadPool {
    public static void main(String[] args) {
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        for (int i=0;i<9;i++){
            singleThreadExecutor.execute(()-> {
                System.out.println("線程名:" + Thread.currentThread().getName());
            });
        }
    }
}
singleThreadExecutor.shutdown();

運行結果只有1個線程:


在這里插入圖片描述

SingleThreadExecutor調用了如下方法構造線程池:


在這里插入圖片描述

CachedThreadPool

一個在需要處理任務時才會創(chuàng)建線程的線程池,如果一個線程處理完任務了還沒有被回收,那么線程可以被重復使用。

當我們調用execute方法時,如果之前創(chuàng)建的線程有空閑可用的,則會復用之前創(chuàng)建好的線程,否則就會創(chuàng)建新的線程加入到線程池中。

創(chuàng)建好的線程如果在60s內沒被使用,那么線程就會被終止并移出緩存。因此,這種線程池可以保持長時間空閑狀態(tài)而不會消耗任何資源。

示例

package com.zwx.concurrent.threadPool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestThreadPool {
    public static void main(String[] args) {
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        for (int i=0;i<9;i++){
            cachedThreadPool.execute(()-> {
                System.out.println("線程名:" + Thread.currentThread().getName());
            });
        }
        cachedThreadPool.shutdown();
}

輸出結果可以看到,創(chuàng)建了9個不同的線程:


在這里插入圖片描述

接下來我們對上面的示例改造一下,在執(zhí)行execute之前休眠一段時間:

package com.zwx.concurrent.threadPool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestThreadPool {
    public static void main(String[] args) {
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        for (int i=0;i<9;i++){
            try {
                    Thread.sleep(i * 10L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            cachedThreadPool.execute(()-> {
                System.out.println("線程名:" + Thread.currentThread().getName());
            });
        }
        cachedThreadPool.shutdown();
}

這時候輸出的結果就只有1個線程了,因為有部分線程可以被復用:


在這里插入圖片描述

注意:這兩個示例的結果都不是固定的,第一種有可能也不會創(chuàng)建9個線程,第二種也有可能不止創(chuàng)建1個線程,具體要看線程的執(zhí)行情況。

CachedThreadPool調用了如下方法構造線程池


在這里插入圖片描述

ScheduledThreadPool

創(chuàng)建一個線程池,它可以在調度命令給定的延遲后運行或定期執(zhí)行。這個相比較于其他的線程池,其自定義了一個子類ScheduledExecutorService繼承了ExecutorService。

示例

package com.zwx.concurrent.threadPool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.Executors;

public class TestThreadPool {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
        for (int i=0;i<9;i++){
            scheduledThreadPool.execute(()->{
                System.out.println("線程名:" + Thread.currentThread().getName());
            });
        }
        scheduledThreadPool.shutdown();
    }
}

輸出結果(執(zhí)行結果具有隨機性,最多只有3個線程執(zhí)行):


在這里插入圖片描述

ScheduledThreadPool最終調用了如下方法構造線程池


在這里插入圖片描述

線程池原理

根據(jù)上面的截圖可以看到,列舉的4中常用的線程池在構造時,最終調用的方法都是ThreadPoolExecutor類的構造方法,所以要分析原理,我們就去看看ThreadPoolExecutor吧!

構造線程池7大參數(shù)

下面就是ThreadPoolExecutor類中最完整的一個構造方法:


在這里插入圖片描述

這個就是是構造線程池的核心方法,總共有7個參數(shù):

  • corePoolSize:核心線程數(shù)量。一直保留在池中的線程,核心線程即使空閑狀態(tài)也不會被回收,除非設置了allowCoreThreadTimeOut屬性
  • maximumPoolSize:最大線程數(shù)量。線程池中允許的最大線程數(shù),大于等于核心線程數(shù)
  • keepAliveTime:活躍時間。當最大線程數(shù)比核心線程數(shù)更大時,超出核心的線程數(shù)的其他線程如果空間時間超過keepAliveTime會被回收
  • TimeUnit:活躍時間的單位
  • BlockingQueue:阻塞隊列。用于存儲尚等待被執(zhí)行的任務。
  • ThreadFactory:創(chuàng)建線程的工廠類
  • RejectedExecutionHandler:拒絕策略。當達到了線程邊界和隊列容量時提交的任務被阻塞時執(zhí)行的策略。

線程池執(zhí)行流程

execute(Runnable) 方法的主流程非常清晰:


在這里插入圖片描述

根據(jù)上面源碼,可以得出線程池執(zhí)行流程圖如下:


在這里插入圖片描述

源碼分析

首先看看ThreadPoolExecutor類中的ctl,是一個32位的int類型,其中將高3位用來表示線程數(shù)量,低29位用來表示,其中的計算方式都是采用二進制來計算。


在這里插入圖片描述

其中各種狀態(tài)的轉換關系如下圖:


在這里插入圖片描述

其中狀態(tài)的大小關系為:
RUNNING<SHUTDOWN<STOP<TIDYING<TERMINATED

addWork方法

 private boolean addWorker(Runnable firstTask, boolean core) {
       //第一段邏輯:線程數(shù)+1
        retry:
        for (;;) {
            int c = ctl.get();//獲取線程池容量
            int rs = runStateOf(c);//獲取狀態(tài)

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN &&//即:SHUTDOWN,STOP,TIDYING,TERMINATED
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))//即:rs==RUNNING,firstTask!=null,queue==null
                return false;//如果已經(jīng)關閉,不接受任務;如果正在運行,且queue為null,也返回false
            for (;;) {
                int wc = workerCountOf(c);//獲取當前的工作線程數(shù)
                //如果工作線程數(shù)大于等于容量或者大于等于核心線程數(shù)(最大線程數(shù)),那么就不能再添加worker
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                if (compareAndIncrementWorkerCount(c))//cas增加線程數(shù),失敗則再次自旋嘗試
                    break retry;
                c = ctl.get();  // Re-read ctl //再次獲取工作線程數(shù)
                if (runStateOf(c) != rs)//不相等說明線程池的狀態(tài)發(fā)生了變化,繼續(xù)自旋嘗試
                    continue retry;
            }
        }

        //第二段邏輯:將線程構造成Worker對象,并添加到線程池
        boolean workerStarted = false;//工作線程是否啟動成功
        boolean workerAdded = false;//工作線程是否添加成功
        Worker w = null;
        try {
            w = new Worker(firstTask);//構建一個worker
            final Thread t = w.thread;//去除worker中的線程
            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)

                    //只有當前線程池是正在運行狀態(tài),[或是 SHUTDOWN 且 firstTask 為空],才能添加到 workers 集合中
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        workers.add(w);//將新創(chuàng)建的 Worker 添加到 workers 集合中
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;//更新線程池中線程的數(shù)量
                        workerAdded = true;//添加線程(worker)成功
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    t.start();//這里就會去執(zhí)行Worker中的run()方法
                    workerStarted = true;//啟動成功
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);//如果啟動線程失敗,需要回滾
        }
        return workerStarted;
    }

這個方法主要就是做兩件事:

  • 一、將線程數(shù)+1
  • 二、將線程構造成Worker對象,加入到線程池中,并調用start()方法啟動線程

Worker對象

在這里插入圖片描述

上面這個方法繼承了AbstractQueuedSynchronizer,前面我們講述AQS同步隊列的時候知道,AQS就是一個同步器,那么既然有線程的同步器,這里為什么不直接使用,反而要繼承之后重寫呢?

這是因為AQS同步器內是支持鎖重入的,但是線程池這里的設計思想是并不希望支持重入,所以才會重寫一個AQS來避免重入。

Worker中state初始化狀態(tài)設置為-1,原因是在初始化Worker對象的時候,在線程真正執(zhí)行runWorker()方法之前,不能被中斷。而一旦線程構造完畢并開始執(zhí)行任務的時候,是允許被中斷的,所以在線程進入runWorker()之后的第一件事就是將state設置為0(無鎖狀態(tài)),也就是允許被中斷。

我們再看看Worker的構造器:


在這里插入圖片描述

addWork方法執(zhí)行到這句:w = new Worker(firstTask);//構建一個worker 的時候,就會調用構造器創(chuàng)建一個Worker對象,state=-1,并且將當前任務作為firstTask,后面再運行的時候會優(yōu)先執(zhí)行firstTask。

上面addWorker方法在worker構造成功之后,就會調用worker.start方法,這時候就會去執(zhí)行Worker中的run()方法,這也是一種委派的方式

run()方法中調用了runWorker(this)方法,這個方法就是真正執(zhí)行任務的方法:

runWorker(this)方法

final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        /**
         * 表示當前worker線程允許中斷,因為new Worker默認的 state=-1,此處是調用
         * Worker類的 tryRelease()方法,state置為 0,
         * 而 interruptIfStarted()中只有 state>=0 才允許調用中斷
         */
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {
                /**
                 * 加鎖,這里加鎖不僅僅是為了防止并發(fā),更是為了當調用shutDown()方法的時候線程不被中斷,
                 * 因為shutDown()的時候在中斷線程之前會調用tryLock方法嘗試獲取鎖,獲取鎖成功才會中斷
                 */
                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
                /**
                 * 如果是以下兩種情況,需要中斷線程
                 * 1.如果state>=STOP,且線程中斷標記為false
                 * 2.如果state<STOP,獲取中斷標記并復位,如果線程被中斷,那么,再次判斷state是否STOP
                 *   如果是的話,且線程中斷標記為false
                 */
                if ((runStateAtLeast(ctl.get(), STOP) ||//狀態(tài)>=STOP
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();//中斷線程
                try {
                    beforeExecute(wt, task);//空方法,我們可以重寫它,在執(zhí)行任務前做點事情,常用于線程池運行的監(jiān)控和統(tǒng)計
                    Throwable thrown = null;
                    try {
                        task.run();//正式調用run()執(zhí)行任務
                    } 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);//執(zhí)行任務之后調用,也是個空方法,我們可以重寫它,在執(zhí)行任務后做點事情,常用于線程池運行的監(jiān)控和統(tǒng)計
                    }
                } finally {
                    task = null;//將任務設置為空,那么下次循環(huán)就會通過getTask()方法從workerQueue中取任務了
                    w.completedTasks++;//任務完成數(shù)+1
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            //核心線程會阻塞在getTask()方法中等待線程,除非設置了允許核心線程被銷毀,
            // 否則正常的情況下只有非核心線程才會執(zhí)行這里
            processWorkerExit(w, completedAbruptly);//銷毀線程
        }
    }

主要執(zhí)行步驟為:

  • 1、首先釋放鎖,因為進入這個方法之后線程允許被中斷
  • 2、首先看看傳入的firstTask是否為空,不為空則優(yōu)先執(zhí)行
  • 3、如果firstTask為空(執(zhí)行完了),則嘗試從getTask()中獲取任務,getTask()就是從隊列l(wèi)里面獲取任務
  • 4、如果獲取到任務則開始執(zhí)行,執(zhí)行的時候需要重新上鎖,因為執(zhí)行任務期間也不允許中斷
  • 5、任務運行前后分別有一個空方法,我們可以在有需要的時候重寫這兩個方法,實現(xiàn)付線程池的監(jiān)控
  • 6、如果獲取不到任務,則會執(zhí)行processWorkerExit方法銷毀線程

getTask()方法

private Runnable getTask() {
        //上一次獲取任務是否超時,第一次進來默認false,第一次自旋后如果超時就會設置為true,則第二次自旋就會返回null
        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.
            /**
             * 1\. 線程池狀態(tài)為shutdown,那么就必須要等到workQueue為空才行,因為shutdown()狀態(tài)是需要執(zhí)行隊列中剩余任務的
             * 2.線程池狀態(tài)為stop,那么就不需要關注workQueue中是否有任務
             */
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();//線程池中的線程數(shù)-1
                return null;//返回null的話,那么runWorker方法中就會跳出循環(huán),執(zhí)行finally中的processWorkerExit方法銷毀線程
            }

            int wc = workerCountOf(c);

            // Are workers subject to culling?
            //1.allowCoreThreadTimeOut-默認false,表示核心線程數(shù)不會超時
            //2.如果總線程數(shù)大于核心線程數(shù),那就說明需要有線程被銷毀
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

            /**
             * 1\. 線程數(shù)量超過maximumPoolSize可能是線程池在運行時被調用了setMaximumPoolSize()
             * 被改變了大小,否則已經(jīng) addWorker()成功的話是不會超過maximumPoolSize。
             * 2.timed && timedOut 如果為 true,表示當前操作需要進行超時控制,并且上次從阻塞隊列中
             * 獲取任務發(fā)生了超時.其實就是體現(xiàn)了空閑線程的存活時間
             */
            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;//如果拿到任務了,返回給worker進行處理
                timedOut = true;//走到這里就說明到了超期時間還沒拿到任務,設置為true,第二次自旋就可以直接返回null
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

這個方法主要步驟為:

  • 1、首先判斷狀態(tài)是不是對的,如果是SHUTDOWN之類不符合要求的狀態(tài),那就直接返回null,并把線程數(shù)-1,而返回null之后前面的方法就會跳出while循環(huán),執(zhí)行銷毀線程流程。
  • 2、判斷下是不是有設置超時時間或者最大線程數(shù)超過了核心線程數(shù)
  • 3、根據(jù)上面的判斷決定是執(zhí)行帶有超時時間的poll方法還是take方法從隊列中獲取元素。
    情況一:如果是執(zhí)行帶超時時間的poll方法,那么時間到了如果還沒取到元素,那么就返回空,這種情況說明當前系統(tǒng)并不繁忙,所以返回null之后線程就會被銷毀;
    情況二:如果是執(zhí)行take方法,根據(jù)第2點的判斷知道,除非我們人為設置了核心線程可以被回收,否則核心線程就是會執(zhí)行take方法,如果獲取不到任務就會一直阻塞等待獲取到任務為止。

processWorkerExit方法

這是銷毀線程的方法,上面的getTask()方法返回空,就會執(zhí)行線程銷毀方法,因為getTask()當中已經(jīng)把線程數(shù)-1了,所以這里可以直接執(zhí)行線程銷毀工作。


在這里插入圖片描述

直接調用的是workers集合的remove()方法,后面還有就是嘗試中止和一些異常異常情況的補償操作。

拒絕策略

JDK默認提供的拒絕策略有如下幾種:

  • AbortPolicy:直接拋出異常,默認策略
  • CallerRunsPolicy:用調用者所在的線程來執(zhí)行任務
  • DiscardOldestPolicy:丟棄阻塞隊列中靠最前的任務,并執(zhí)行當前任務
  • DiscardPolicy:直接丟棄任務

我們也可以自定義自己的拒絕策略,只要實現(xiàn)RejectedExecutionHandler接口,重寫其中的唯一一個方法rejectedExecution就可以了。

常見的面試問題

線程池這一塊面試非常喜歡問,我們來舉幾個常見的問題:

問題一

Q:為什么不建議直接使用Executors來構建線程池?

A:用Executors 使得我們不用關心線程池的參數(shù)含義,這樣可能會導致問題,比如我們用newFixdThreadPool或者newSingleThreadPool.允許的隊列長度為Integer.MAX_VALUE,如果使用不當會導致大量請求堆積到隊列中導致OOM的風險而newCachedThreadPool,允許創(chuàng)建線程數(shù)量為 Integer.MAX_VALUE,也可能會導致大量 線程的創(chuàng)建出現(xiàn)CPU使用過高或者OOM的問題。而如果我們通過ThreadPoolExecutor來構造線程池的話,我們勢必要了解線程池構造中每個 參數(shù)的具體含義,會更加謹慎。

問題二

Q:如何合理配置線程池的大小?

A:要想合理地配置線程池,就必須首先分析任務特性,可以從以下幾個角度來分析:

  • 任務的性質:CPU密集型任務、IO密集型任務和混合型任務。
  • 任務的優(yōu)先級:高、中和低。
  • 任務的執(zhí)行時間:長、中和短。
  • 任務的依賴性:是否依賴其他系統(tǒng)資源,如數(shù)據(jù)庫連接。

CPU密集型:
CPU密集型的特點是響應時間很快,cpu一直在運行,這種任務cpu 的利用率很高,那么線程數(shù)的配置應該根據(jù)CPU核心數(shù)來決定,CPU核心數(shù)=最大同時執(zhí)行線程數(shù),假如CPU核心數(shù)為4,那么服務器最多能同時執(zhí)行4個線程。過多的線程會導致上 下文切換反而使得效率降低。那線程池的最大線程數(shù)可以配置為cpu核心數(shù)+1。

IO密集型:
主要是進行IO操作,執(zhí)行IO操作的時間較長,這是cpu會處于空閑狀態(tài), 導致cpu的利用率不高,這種情況下可以增加線程池的大小。可以結合線程的等待時長來做判斷,等待時間越高,那么線程數(shù)也相對越多。一般可以配置cpu核心數(shù)的2倍。
一個公式:線程池設定最佳線程數(shù)目 = ((線程池設定的線程等待時間+線程 CPU 時間)/ 線程CPU時間 )* CPU數(shù)目

附:獲取CPU個數(shù)方法:Runtime.getRuntime().availableProcessors()

問題三

Q:線程池中的核心線程什么時候會初始化?

A:默認情況下,創(chuàng)建線程池之后,線程池中是沒有線程的,需要提交任務之后才會創(chuàng)建線程。 在實際中如果需要線程池創(chuàng)建之后立即創(chuàng)建線程,可以通過如下兩個方法:

  • prestartCoreThread():初始化一個核心線程。
  • prestartAllCoreThreads():初始化所有核心線程

問題四

Q:線程池被關閉時,如果還有任務在執(zhí)行,怎么辦?

A:線程池的關閉有兩個方法:

  • shutdown()
    不會立即終止線程池,要等所有任務緩存隊列中的任務都執(zhí)行完后才終止,但是不會接受新的任務
  • shutdownNow()
    立即終止線程池,并嘗試打斷正在執(zhí)行的任務,并且清空任務緩存隊列,返回尚未執(zhí)行的任務任務

問題五

Q:線程池容量是否可以動態(tài)調整?

A:可以通過兩個方法動態(tài)調整線程池的大小。

  • setCorePoolSize():設置最大核心線程數(shù)
  • setMaximumPoolSize():設置最大工作線程數(shù)

總結

本文從線程池的常見的四種用法使用示例開始入手,最終發(fā)現(xiàn)都調用了同一個類去構造線程池(ThreadPoolExecutor),所以我們就從ThreadPoolExecutor構造器開始分析了構建一個線程池的7大參數(shù),并從execute()方法開始逐步分析了線程池的使用原理,當然,其實線程池還有一個方法submit()也可以作為入口,這個會放在下篇并發(fā)系列講述Future/Callable的時候再去分析。

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