java線程池原理

前言:
線程是稀缺資源,如果被無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,合理的使用線程池對線程進行統一分配、調優和監控,有以下好處:
1、降低資源消耗;
2、提高響應速度;
3、提高線程的可管理性。

線程池用一個32位的int來同時保存runState和workerCount,其中高3位是runState,其余29位是workerCount。代碼中會反復使用runStateOf和workerCountOf來獲取runState和workerCount。


2184951-16c59af5d9d66527.png

1、Executors.newFixedThreadPool(10)初始化一個包含10個線程的線程池executor;
2、通過executor.execute方法提交20個任務,每個任務打印當前的線程名;
3、負責執行任務的線程的生命周期都由Executor框架進行管理;

ThreadPoolExecutor
Executors是java線程池的工廠類,通過它可以快速初始化一個符合業務需求的線程池,如Executors.newFixedThreadPool方法可以生成一個擁有固定線程數的線程池。


2184951-35585a83d35ee516.png

其本質是通過不同的參數初始化一個ThreadPoolExecutor對象,具體參數描述如下:
corePoolSize:
線程池中的核心線程數,當提交一個任務時,線程池創建一個新線程執行任務,直到當前線程數等于corePoolSize;如果當前線程數為corePoolSize,繼續提交的任務被保存到阻塞隊列中,等待被執行;如果執行了線程池的prestartAllCoreThreads()方法,線程池會提前創建并啟動所有核心線程。

maximumPoolSize:
線程池中允許的最大線程數。如果當前阻塞隊列滿了,且繼續提交任務,則創建新的線程執行任務,前提是當前線程數小于maximumPoolSize;

keepAliveTime:
線程空閑時的存活時間,即當線程沒有任務執行時,繼續存活的時間;默認情況下,該參數只在線程數大于corePoolSize時才有用;

unit:
keepAliveTime的單位;

workQueue:
用來保存等待被執行的任務的阻塞隊列,且任務必須實現Runable接口,在JDK中提供了如下阻塞隊列:
1、ArrayBlockingQueue:基于數組結構的有界阻塞隊列,按FIFO排序任務;
2、LinkedBlockingQuene:基于鏈表結構的阻塞隊列,按FIFO排序任務,吞吐量通常要高于ArrayBlockingQuene;
3、SynchronousQuene:一個不存儲元素的阻塞隊列,每個插入操作必須等到另一個線程調用移除操作,否則插入操作一直處于阻塞狀態,吞吐量通常要高于LinkedBlockingQuene;
4、priorityBlockingQuene:具有優先級的無界阻塞隊列;

threadFactory:
創建線程的工廠,通過自定義的線程工廠可以給每個新建的線程設置一個具有識別度的線程名。


2184951-d2d8fd007c7f7a27.png

handler:
線程池的飽和策略,當阻塞隊列滿了,且沒有空閑的工作線程,如果繼續提交任務,必須采取一種策略處理該任務,線程池提供了4種策略:
1、AbortPolicy:直接拋出異常,默認策略;
2、CallerRunsPolicy:用調用者所在的線程來執行任務;
3、DiscardOldestPolicy:丟棄阻塞隊列中靠最前的任務,并執行當前任務;
4、DiscardPolicy:直接丟棄任務;
當然也可以根據應用場景實現RejectedExecutionHandler接口,自定義飽和策略,如記錄日志或持久化存儲不能處理的任務。

Exectors:
Exectors工廠類提供了線程池的初始化接口,主要有如下幾種:
newFixedThreadPool


2184951-35585a83d35ee516.png

初始化一個指定線程數的線程池,其中corePoolSize == maximumPoolSize,使用LinkedBlockingQuene作為阻塞隊列,不過當線程池沒有可執行任務時,也不會釋放線程。

newCachedThreadPool


2184951-9b76630ac48f318c.png

1、初始化一個可以緩存線程的線程池,默認緩存60s,線程池的線程數可達到Integer.MAX_VALUE,即2147483647,內部使用SynchronousQueue作為阻塞隊列;
2、和newFixedThreadPool創建的線程池不同,newCachedThreadPool在沒有任務執行時,當線程的空閑時間超過keepAliveTime,會自動釋放線程資源,當提交新任務時,如果沒有空閑線程,則創建新線程執行任務,會導致一定的系統開銷;

所以,使用該線程池時,一定要注意控制并發的任務數,否則創建大量的線程可能導致嚴重的性能問題

newSingleThreadExecutor


2184951-deded05302aaf255.png

初始化的線程池中只有一個線程,如果該線程異常結束,會重新創建一個新的線程繼續執行任務,唯一的線程可以保證所提交任務的順序執行,內部使用LinkedBlockingQueue作為阻塞隊列。

newScheduledThreadPool


2184951-14823f17e4e4a09e.png

初始化的線程池可以在指定的時間內周期性的執行所提交的任務,在實際的業務場景中可以使用該線程池定期的同步數據。

實現原理
除了newScheduledThreadPool的內部實現特殊一點之外,其它幾個線程池都是基于ThreadPoolExecutor類實現的。

線程池內部狀態


2184951-5a620e0f56cbb008.png

其中AtomicInteger變量ctl的功能非常強大:利用低29位表示線程池中線程數,通過高3位表示線程池的運行狀態:
1、RUNNING:-1 << COUNT_BITS,即高3位為111,該狀態的線程池會接收新任務,并處理阻塞隊列中的任務;
2、SHUTDOWN: 0 << COUNT_BITS,即高3位為000,該狀態的線程池不會接收新任務,但會處理阻塞隊列中的任務;
3、STOP : 1 << COUNT_BITS,即高3位為001,該狀態的線程不會接收新任務,也不會處理阻塞隊列中的任務,而且會中斷正在運行的任務;
4、TIDYING : 2 << COUNT_BITS,即高3位為010;
5、TERMINATED: 3 << COUNT_BITS,即高3位為011;

線程池狀態默認從RUNNING開始流轉,到狀態TERMINATED結束,中間不需要經過每一種狀態,但不能讓狀態回退。下面是狀態變化可能的路徑和變化條件:

3294095-6a507005d933355b.png

任務提交
線程池框架提供了兩種方式提交任務,根據不同的業務需求選擇不同的方式。

Executor.execute()


2184951-834971c24d085d31.png

通過Executor.execute()方法提交的任務,必須實現Runnable接口,該方式提交的任務不能獲取返回值,因此無法判斷任務是否執行成功。

ExecutorService.submit()


2184951-ea9fe289ca3de89f.png

通過ExecutorService.submit()方法提交的任務,可以獲取任務執行完的返回值。

任務執行
當向線程池中提交一個任務,線程池會如何處理該任務?

execute實現:
調用execute將會根據線程池的情況創建Worker,可以歸納出下圖四種情況:


3294095-c3b458db36a11d5e.png

標記1對應第一種情況,要留意addWorker傳入了core,core=true為corePoolSize,core=false為maximumPoolSize,新增時需要檢查workerCount是否超過允許的最大值。
標記2對應第二種情況,檢查線程池是否在運行,并且將任務加入等待隊列。標記3再檢查一次線程池狀態,如果線程池忽然處于非運行狀態,那就將等待隊列剛加的任務刪掉,再交給RejectedExecutionHandler處理。標記4發現沒有worker,就先補充一個空任務的worker。
標記5對應第三種情況,等待隊列不能再添加任務了,調用addWorker添加一個去處理。
標記6對應第四種情況,addWorker的core傳入false,返回調用失敗,代表workerCount已經超出maximumPoolSize,那就交給RejectedExecutionHandler處理。

addWorker實現
從方法execute的實現可以看出:addWorker主要負責創建新的線程并執行任務,代碼實現如下:

private boolean addWorker(Runnable firstTask, boolean core) {
        //1
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            for (;;) {
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }
        //2
        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            w = new Worker(firstTask);
            final Thread t = w.thread;
            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());

                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        workers.add(w);
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

在addWoker的標記1部分:
1、判斷線程池的狀態,如果線程池的狀態值大于或等SHUTDOWN,則不處理提交的任務,直接返回;
2、通過參數core判斷當前需要創建的線程是否為核心線程,如果core為true,且當前線程數小于corePoolSize,則跳出循環,開始創建新的線程,具體實現在標記2部分。

在標記2部分中,線程池的工作線程通過Woker類實現,在ReentrantLock鎖的保證下,把Woker實例插入到HashSet后,并啟動Woker中的線程,其中Worker類設計如下:
1、繼承了AQS類,可以方便的實現工作線程的中止操作;
2、實現了Runnable接口,可以將自身作為一個任務在工作線程中執行;
3、當前提交的任務firstTask作為參數傳入Worker的構造方法;

Worker的執行

Worker(Runnable firstTask) {
    setState(-1); // inhibit interrupts until runWorker
    this.firstTask = firstTask;
    this.thread = getThreadFactory().newThread(this);
}

public void run() {
    runWorker(this);
}

從Woker類的構造方法實現可以發現:線程工廠在創建線程thread時,將Woker實例本身this作為參數傳入,當執行start方法啟動線程thread時,本質是執行了Worker的runWorker方法。

runWorker實現:


2184951-1e8ed00138c189ea.png

runWorker方法是線程池的核心:
1、線程啟動之后,通過unlock方法釋放鎖,設置AQS的state為0,表示運行中斷;
2、獲取第一個任務firstTask,執行任務的run方法,不過在執行任務之前,會進行加鎖操作,任務執行完會釋放鎖;
3、在執行任務的前后,可以根據業務場景自定義beforeExecute和afterExecute方法;
4、firstTask執行完成之后,通過getTask方法從阻塞隊列中獲取等待的任務,如果隊列中沒有任務,getTask方法會被阻塞并掛起,不會占用cpu資源;

getTask實現:


2184951-a63a6646c456f715.png

整個getTask操作在自旋下完成:
1、workQueue.take:如果阻塞隊列為空,當前線程會被掛起等待;當隊列中有任務加入時,線程被喚醒,take方法返回任務,并執行;
2、workQueue.poll:如果在keepAliveTime時間內,阻塞隊列還是沒有任務,則返回null;

所以,線程池中實現的線程可以一直執行由用戶提交的任務。

Future和Callable實現
通過ExecutorService.submit()方法提交的任務,可以獲取任務執行完的返回值。


2184951-cdacf86769a61288.png

在實際業務場景中,Future和Callable基本是成對出現的,Callable負責產生結果,Future負責獲取結果。
1、Callable接口類似于Runnable,只是Runnable沒有返回值。
2、Callable任務除了返回正常結果之外,如果發生異常,該異常也會被返回,即Future可以拿到異步執行任務各種結果;
3、Future.get方法會導致主線程阻塞,直到Callable任務執行完成;

submit實現:


2184951-15281a586352e509.png

通過submit方法提交的Callable任務會被封裝成了一個FutureTask對象。

FutureTask:


2184951-d109631c519443cc.png

1、FutureTask在不同階段擁有不同的狀態state,初始化為NEW;
2、FutureTask類實現了Runnable接口,這樣就可以通過Executor.execute方法提交FutureTask到線程池中等待被執行,最終執行的是FutureTask的run方法;

FutureTask.get實現:


2184951-62fdfdd3f3a14e22.png

內部通過awaitDone方法對主線程進行阻塞,具體實現如下:


2184951-aa841730137097ac.png

1、如果主線程被中斷,則拋出中斷異常;
2、判斷FutureTask當前的state,如果大于COMPLETING,說明任務已經執行完成,則直接返回;
3、如果當前state等于COMPLETING,說明任務已經執行完,這時主線程只需通過yield方法讓出cpu資源,等待state變成NORMAL;
4、通過WaitNode類封裝當前線程,并通過UNSAFE添加到waiters鏈表;
5、最終通過LockSupport的park或parkNanos掛起線程;

FutureTask.run實現:


2184951-29642db6d9c6589e.png

FutureTask.run方法是在線程池中被執行的,而非主線程
1、通過執行Callable任務的call方法;
2、如果call執行成功,則通過set方法保存結果;
3、如果call執行有異常,則通過setException保存異常;

set:


2184951-19a5160726da146d.png

setException:


2184951-34a8bba028ba58be.png

set和setException方法中,都會通過UnSAFE修改FutureTask的狀態,并執行finishCompletion方法通知主線程任務已經執行完成;

finishCompletion:


2184951-c380e58cdcb02e31.png

1、執行FutureTask類的get方法時,會把主線程封裝成WaitNode節點并保存在waiters鏈表中;
2、FutureTask任務執行完成后,通過UNSAFE設置waiters的值,并通過LockSupport類unpark方法喚醒主線程;

最后來看結束worker需要執行的操作:

private void processWorkerExit(Worker w, boolean completedAbruptly) {
   //1
    if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
        decrementWorkerCount();

  //2
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        completedTaskCount += w.completedTasks;
        workers.remove(w);
    } finally {
        mainLock.unlock();
    }

   //3
    tryTerminate();

    int c = ctl.get();
    //4
    if (runStateLessThan(c, STOP)) {
        if (!completedAbruptly) {
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            if (min == 0 && ! workQueue.isEmpty())
                min = 1;
            if (workerCountOf(c) >= min)
                return; // replacement not needed
        }
        addWorker(null, false);
    }
}

正常情況下,在getTask里就會將workerCount減一。標記1處用變量completedAbruptly判斷worker是否異常退出,如果是,需要補充對workerCount的減一。
標記2將worker處理任務的數量累加到總數,并且在集合workers中去除。
標記3嘗試終止線程池,后續會研究。
標記4處理線程池還是RUNNING或SHUTDOWN狀態時,如果worker是異常結束,那么會直接addWorker。如果allowCoreThreadTimeOut=true,并且等待隊列有任務,至少保留一個worker;如果allowCoreThreadTimeOut=false,workerCount不少于corePoolSize。

線程池的關閉
線程池的關閉不是一關了事,worker在池里處于不同狀態,必須安排好worker的"后事",才能真正釋放線程池。ThreadPoolExecutor提供兩種方法關閉線程池:

shutdown:不能再提交任務,已經提交的任務可繼續運行;
shutdownNow:不能再提交任務,已經提交但未執行的任務不能運行,在運行的任務可繼續運行,但會被中斷,返回已經提交但未執行的任務。

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();   //1 安全策略機制
        advanceRunState(SHUTDOWN);   //2
        interruptIdleWorkers();   //3
        onShutdown(); //4 空方法,子類實現
    } finally {
        mainLock.unlock();
    }
    tryTerminate();   //5
}

shutdown將線程池切換到SHUTDOWN狀態,并調用interruptIdleWorkers請求中斷所有空閑的worker,最后調用tryTerminate嘗試結束線程池。

public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(STOP);
        interruptWorkers();
        tasks = drainQueue();  //1
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
    return tasks;
}

shutdownNow和shutdown類似,將線程池切換為STOP狀態,中斷目標是所有worker。drainQueue會將等待隊列里未執行的任務返回。

interruptIdleWorkers和interruptWorkers實現原理都是遍歷workers集合,中斷條件符合的worker。

上面的代碼多次出現調用tryTerminate,這是一個嘗試將線程池切換到TERMINATED狀態的方法。

final void tryTerminate() {
    for (;;) {
        int c = ctl.get();
        //1
        if (isRunning(c) ||
            runStateAtLeast(c, TIDYING) ||
            (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
            return;
        //2
        if (workerCountOf(c) != 0) { // Eligible to terminate
            interruptIdleWorkers(ONLY_ONE);
            return;
        }
       //3
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                try {
                    terminated();
                } finally {
                    ctl.set(ctlOf(TERMINATED, 0));
                    termination.signalAll();
                }
                return;
            }
        } finally {
            mainLock.unlock();
        }
        // else retry on failed CAS
    }
}

標記1檢查線程池狀態,下面幾種情況,后續操作都沒有必要,直接return。

RUNNING(還在運行,不能停)
TIDYING或TERMINATED(已經沒有在運行的worker)
SHUTDOWN并且等待隊列非空(執行完才能停)
標記2在worker非空的情況下又調用了interruptIdleWorkers,你可能疑惑在shutdown時已經調用過了,為什么又調用,而且每次只中斷一個空閑worker?你需要知道,shutdown時worker可能在執行中,執行完阻塞在隊列的take,不知道要結束,所有要補充調用interruptIdleWorkers。每次只中斷一個是因為processWorkerExit時,還會執行tryTerminate,自動中斷下一個空閑的worker。

標記3是最終的狀態切換。線程池會先進入TIDYING狀態,再進入TERMINATED狀態,中間提供了terminated這個空方法供子類實現。

調用關閉線程池方法后,需要等待線程池切換到TERMINATED狀態。awaitTermination檢查限定時間內線程池是否進入TERMINATED狀態,代碼如下:

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

參考文章:
http://www.lxweimin.com/p/87bff5cc8d8c
http://www.lxweimin.com/p/f62a3f452869

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

推薦閱讀更多精彩內容