執行緒池的實現原理

流浪的雨發表於2019-03-12

執行緒池的優點

1、執行緒是稀缺資源,使用執行緒池可以減少建立和銷燬執行緒的次數,每個工作執行緒都可以重複使用。

2、可以根據系統的承受能力,調整執行緒池中工作執行緒的數量,防止因為消耗過多記憶體導致伺服器崩潰。

執行緒池的建立

public ThreadPoolExecutor(int corePoolSize,
                                int maximumPoolSize,
                                long keepAliveTime,
                                TimeUnit unit,
                                BlockingQueue<Runnable> workQueue,
                                RejectedExecutionHandler handler) 
複製程式碼
corePoolSize:執行緒池核心執行緒數量

maximumPoolSize:執行緒池最大執行緒數量

keepAliverTime:當活躍執行緒數大於核心執行緒數時,空閒的多餘執行緒最大存活時間

unit:存活時間的單位

workQueue:存放任務的佇列

handler:超出執行緒範圍和佇列容量的任務的處理程式

注:關於workQueue引數的取值,JDK提供了4種阻塞佇列型別供選擇:
            ArrayBlockingQueue:基於陣列結構的有界阻塞佇列,按FIFO排序任務;
            
            inkedBlockingQuene:基於連結串列結構的阻塞佇列,按FIFO排序任務,吞吐量通常要高於ArrayBlockingQuene 

            SynchronousQuene:一個不儲存元素的阻塞佇列,每個插入操作必須等到另一個執行緒呼叫移除操作,否則插入操作一直處於阻塞狀態,吞吐量通常要高於ArrayBlockingQuene;

            PriorityBlockingQuene:具有優先順序的無界阻塞佇列;

     threadFactory:執行緒工廠,主要用來建立執行緒;

     handler:表示當拒絕處理任務時的策略,有以下四種取值

 注: 當執行緒池的飽和策略,當阻塞佇列滿了,且沒有空閒的工作執行緒,如果繼續提交任務,必須採取一種策略處理該任務,執行緒池提供了4種策略:

        ThreadPoolExecutor.AbortPolicy:丟棄任務並丟擲RejectedExecutionException異常。

        ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不丟擲異常。

        ThreadPoolExecutor.DiscardOldestPolicy:丟棄佇列最前面的任務,然後重新嘗試執行任務(重複此過程)

        ThreadPoolExecutor.CallerRunsPolicy:由呼叫執行緒處理該任務

        當然也可以根據應用場景實現RejectedExecutionHandler介面,自定義飽和策略,如記錄日誌或持久化儲存不能處理的任務。


複製程式碼

執行緒池的實現原理

提交一個任務到執行緒池中,執行緒池的處理流程如下:

1、判斷執行緒池裡的核心執行緒是否都在執行任務,如果不是(核心執行緒空閒或者還有核心執行緒沒有被建立)則建立一個新的工作執行緒來執行任務。如果核心執行緒都在執行任務,則進入下個流程。

2、執行緒池判斷工作佇列是否已滿,如果工作佇列沒有滿,則將新提交的任務儲存在這個工作佇列裡。如果工作佇列滿了,則進入下個流程。

3、判斷執行緒池裡的執行緒是否都處於工作狀態,如果沒有,則建立一個新的工作執行緒來執行任務。如果已經滿了,則交給飽和策略來處理這個任務。
複製程式碼

執行緒池的實現原理

執行緒池的原始碼解讀

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
            //如果執行緒數大於等於基本執行緒數或者執行緒建立失敗,將任務加入佇列
        if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
           //執行緒池處於執行狀態並且加入佇列成功
            if (runState == RUNNING && workQueue.offer(command)) {
                if (runState != RUNNING || poolSize == 0)
                    ensureQueuedTaskHandled(command);
            }
           //執行緒池不處於執行狀態或者加入佇列失敗,則建立執行緒(建立的是非核心執行緒)
            else if (!addIfUnderMaximumPoolSize(command))
          //建立執行緒失敗,則採取阻塞處理的方式
                reject(command); // is shutdown or saturated
        }
    }
複製程式碼

初始化四種型別的執行緒池

1、newFixedThreadPool()

說明:初始化一個指定執行緒數的執行緒池,其中corePoolSize == maxiPoolSize,使用LinkedBlockingQuene作為阻塞佇列
特點:即使當執行緒池沒有可執行任務時,也不會釋放執行緒。
複製程式碼

2、newCachedThreadPool()

說明:初始化一個可以快取執行緒的執行緒池,預設快取60s,執行緒池的執行緒數可達到Integer.MAX_VALUE,即2147483647,內部使用SynchronousQueue作為阻塞佇列;
特點:在沒有任務執行時,當執行緒的空閒時間超過keepAliveTime,會自動釋放執行緒資源;當提交新任務時,如果沒有空閒執行緒,則建立新執行緒執行任務,會導致一定的系統開銷;
因此,使用時要注意控制併發的任務數,防止因建立大量的執行緒導致而降低效能。
複製程式碼

3、newSingleThreadExecutor()

說明:初始化只有一個執行緒的執行緒池,內部使用LinkedBlockingQueue作為阻塞佇列。
特點:如果該執行緒異常結束,會重新建立一個新的執行緒繼續執行任務,唯一的執行緒可以保證所提交任務的順序執行
複製程式碼

4、newScheduledThreadPool()

特定:初始化的執行緒池可以在指定的時間內週期性的執行所提交的任務,在實際的業務場景中可以使用該執行緒池定期的同步資料。

總結:除了newScheduledThreadPool的內部實現特殊一點之外,其它執行緒池內部都是基於ThreadPoolExecutor類(Executor的子類)實現的。
複製程式碼

向執行緒池提交任務

有兩種方式:

  Executor.execute(Runnable command);

  ExecutorService.submit(Callable<T> task);
複製程式碼

execute()的內部實現

1.首次通過workCountof()獲知當前執行緒池中的執行緒數,

  如果小於corePoolSize, 就通過addWorker()建立執行緒並執行該任務;

&emsp;否則,將該任務放入阻塞佇列;

2. 如果能成功將任務放入阻塞佇列中,  

如果當前執行緒池是非RUNNING狀態,則將該任務從阻塞佇列中移除,然後執行reject()處理該任務;

如果當前執行緒池處於RUNNING狀態,則需要再次檢查執行緒池(因為可能在上次檢查後,有執行緒資源被釋放),是否有空閒的執行緒;如果有則執行該任務;

3、如果不能將任務放入阻塞佇列中,說明阻塞佇列已滿;那麼將通過addWoker()嘗試建立一個新的執行緒去執行這個任務;如果addWoker()執行失敗,說明執行緒池中執行緒數達到maxPoolSize,則執行reject()處理任務;
複製程式碼

sumbit()內部實現

會將提交的Callable任務會被封裝成了一個FutureTask物件

FutureTask類實現了Runnable介面,這樣就可以通過Executor.execute()提交FutureTask到執行緒池中等待被執行,最終執行的是FutureTask的run方法; 

比較:

 兩個方法都可以向執行緒池提交任務,execute()方法的返回型別是void,它定義在Executor介面中, 而submit()方法可以返回持有計算結果的Future物件,它定義在ExecutorService介面中,它擴充套件了Executor介面,其它執行緒池類像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有這些方法。 
複製程式碼

相關文章