【多線程】線程池源碼(一)

上一篇文章講了有關線程池的一些簡單的用法,這篇文章主要是從源碼的角度進一步帶你們瞭解線程池的工做流程和工做原理。java

首先先來回顧下如何使用線程池開啓線程多線程

private static void createThreadByThreadPoolExecutor() {
    ThreadPoolExecutor executor = new ThreadPoolExecutor(5,5, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
    for (int i = 0; i < 10; i++) {
        MyThread myThread = new MyThread();
        executor.execute(myThread);
    }

能夠看到其實沒有其它特殊的地方,除了構建線程池的代碼,其它最終要的就是executor.execute(myThread) 行代碼了。函數

準備工做

在多線程系列的第一篇文章中提到了線程和進程的狀態,線程池一樣也有狀態,以下:spa

  • Running: 容許接受新的任務,而且處理隊列中的任務
  • Shutdown: 不接受新的任務,可是仍然會處理隊列中的任務
  • Stop: 不接受新的任務,不處理隊列中的任務,並且中端在運行的任務
  • Tidying: 全部的任務都已經終端,而且工做線程數歸0,該狀態下的線程都會調用terminated() 函數
  • Terminated:terminated() 函數調用完後就進入了此狀態

首先須要知道4個概念WorkerworkersworkQueuetask.線程

  • 在線程池中有個比較重要的類,那就是Worker ,能夠看到其實現了Runnable接口(其實就是工做線程),繼承了AbstractQueuedSynchronizer類(俗稱AQS,在多線程中是很重要的類)code

  • workers 就是Worker 的一個集合,private final HashSet<Worker> workers = new HashSet<Worker>();
  • task :須要執行的任務,也就是execute() 中的參數,實現了Runnable接口
  • workQueue 就是工做隊列,就是上一篇文章中線程池構造函數中的工做隊列,裏面存儲的就是須要執行的任務,隊列是實現BlockingQueue接口的類,有如下這些實現對象

execute()

本方法傳進去的類是須要實現Runnable接口的,做爲一個command 傳進去繼承

遇到新的任務接口

  1. 若是工做線程數 < 核心線程數,那麼直接加1個worker
  2. 若是線程池是正常的工做狀態,而且工做隊列可以添加任務,此時須要第二輪判斷隊列

    1. 若是線程池由於某種緣由不正常了,而且可以成功從工做隊列中刪除任務,那麼直接採起拒絕策略
    2. 若是此時工做線程數爲0,此時須要新建一個線程(而且這裏建立的是非核心線程)來執行這個任務,爲何是null呢,由於已經把任務放在工做隊列裏面了。若是新建的worker的firstTask是該任務的話,就會重複執行兩次任務。
    3. 其它狀況也就是正常狀況下,是啥都不幹,由於主要目的是把任務放到工做隊列中
  3. 若是工做隊列已經滿了,則須要判斷是否可以成功添加一個非核心線程,若是連非核心線程數都知足不了了,就是線程池真的搞不了了,累了,因此也會調用拒絕策略。
注意:核心線程和非核心線程只是語義上的說法, 沒有本質上的區別

addworker()

addworker 的做用是檢查是否能夠根據當前池狀態和給定界限(核心或最大值)添加新線程 ,而且經過第二個參數來斷定是否建立核心線程,當爲true的時候就是核心線程,反之就是非核心線程。源碼裏的註釋以下

來看看代碼具體是如何的

  1. 一進來就是一個死循環,這個死循環最主要的目的是確認線程池狀態是否正常。若是線程池的狀態大於SHUTDOWN,也就是處於STOP、TIDYING或者TERMINATED的時候,線程池都沒了,還建立worker幹啥,直接返回fasle;當線程池處於SHUTDOWN的時候,又得再次判斷:

    1. 傳進來的任務若是不爲空,那麼就不用添加worker的,銀行下班了,辦理完如今的顧客就不在辦理了,否則一直來那不是一直不能下班。
    2. 傳進來的任務爲空,可是工做隊列爲空,一樣不用添加worker,銀行都沒有任務了,並且又到點下班了。除了以上3中狀況返回false,其它狀況不作任何反應,往下走。
  2. 又是一個死循環,首先獲得工做線程數若是超過了邊界,好比超過了容量、核心線程數或者最大線程數,就不用添加worker了,銀行實在是辦理不了新的顧客了;當工做線程數正常的狀況下,經過CAS來增長工做線程數,若是能增長成功就退出最外層循環。若是增長工做線程失敗,那就是其它線程增長了該數量,若是此時線程池的運行狀態發生了改變,則重複外層循環,不然就自旋直到成功增長工做線程數。
  3. 到這裏就能夠說基本掃除了障礙,以傳進來的firstTask新建一個Worker,而後獲取Worker裏的線程。若是線程爲null,那麼就直接執行添加Worker失敗的邏輯,不然就是正常的邏輯。
  4. 先來看正常的邏輯,拿到鎖,而且開始又一次的獲取線程池的狀態

    1. 若是線程是正常運行狀態,或者說是關閉狀態下firstTask仍然爲null,此時若是線程是alive 那麼而說明線程已經開啓,直接拋出異常。
    2. 正常狀況下是wokers集合中添加新的worker元素,而且調整線程池最大值,設置workerAdded標誌爲true。
    3. 重點 ,當workerAdded爲true的時候,開啓工做線程,也就是代碼中的t.start()
  5. 再來看失敗的邏輯 ,是在第3點構造一個Worker對象,獲取線程的時候,有可能獲取到的線程爲空,這樣其實就是增長worker失敗,返回false,在返回false以前會觸發執行失敗的邏輯addWorkerFailed() 邏輯。整個的邏輯是比較簡單的,就再也不花費篇幅去闡述。

到這裏,整個addWorker()的流程是比較清晰的,值得一提的就是第2行代碼中的retry 這個看起來是關鍵字,但其實不是,僅僅只是一個相似標誌位的東西,能夠是retry,也能夠是abc。

一般是配合for循環來使用,搭配continue和break能夠達到goto的效果。好比continue retry; 就是跳到一開始最外層的for循環,break retry; 至關於退出整個循環。寫一個最簡單的例子你們都能知道了。

public class RetryExample {
    public static void main(String[] args) {
        abc:
        for (int i = 0; i < 10; i++) {
            for (int j = 0; j < 10; j++) {
                if (j == 2) {
                    continue abc;
                }
                if (i == 8) {
                    break abc;
                }
                System.out.println("i: " + i + ", j: " + j);
            }
        }
    }
}

輸出結果以下圖所示

創做不易,若是對你有幫助,歡迎點贊,收藏和分享啦!

下面是我的公衆號,有興趣的能夠關注一下,說不定就是你的寶藏公衆號哦,基本2,3天1更技術文章!!!

相關文章
相關標籤/搜索