Java面試進階:Java併發類庫提供的線程池有哪幾種? 分別有什麼特點?

Executors 目前提供了 5 種不同的線程池創建配置:

newCachedThreadPool(),它是一種用來處理大量短時間工作任務的線程池,具有幾個鮮明特點:它會試圖緩存線程並重用,當無緩存線程可用時,就會創建新的工作線程;如果線程閒置的時間超過 60 秒,則被終止並移出緩存;長時間閒置時,這種線程池,不會消耗什麼資源。其內部使用 SynchronousQueue 作爲工作隊列。

newFixedThreadPool(int nThreads),重用指定數目(nThreads)的線程,其背後使用的是無界的工作隊列,任何時候最多有 nThreads 個工作線程是活動的。這意味着,如果任務數量超過了活動隊列數目,將在工作隊列中等待空閒線程出現;如果有工作線程退出,將會有新的工作線程被創建,以補足指定的數目 nThreads。

newSingleThreadExecutor(),它的特點在於工作線程數目被限制爲 1,操作一個無界的工作隊列,所以它保證了所有任務的都是被順序執行,最多會有一個任務處於活動狀態,並且不允許使用者改動線程池實例,因此可以避免其改變線程數目。

newSingleThreadScheduledExecutor() 和 newScheduledThreadPool(int corePoolSize),創建的是個 ScheduledExecutorService,可以進行定時或週期性的工作調度,區別在於單一工作線程還是多個工作線程。

newWorkStealingPool(int parallelism),這是一個經常被人忽略的線程池,Java 8 才加入這個創建方法,其內部會構建ForkJoinPool,利用Work-Stealing算法,並行地處理任務,不保證處理順序。

線程池內部實現:

Executor 框架的基本組成:

ScheduledThreadPoolExecutor 是 ThreadPoolExecutor 的擴展,主要是增加了調度邏輯。而 ForkJoinPool 則是爲 ForkJoinTask 定製的線程池,與通常意義的線程池有所不同。

1)Executor 是一個基礎的接口,其初衷是將任務提交和任務執行細節解耦

2)ExecutorService 則更加完善,不僅提供 service 的管理功能,比如 shutdown 等方法,也提供了更加全面的提交任務機制,如返回Future而不是 void 的 submit 方法。

3)Java 標準類庫提供了ThreadPoolExecutor、ScheduledThreadPoolExecutor、ForkJoinPool。

4)Executors 則從簡化使用的角度,爲我們提供了各種方便的靜態工廠方法。

 

設計與實現:

1)工作隊列:負責存儲用戶提交的各個任務,

newCachedThreadPool——》SynchronousQueue可以是容量爲 0 ,大小浮動;

newFixedThreadPool——》LinkedBlockingQueue固定大小線程池。

2)內部的“工作線程管理池”指保持工作線程的集合運行過程中管理線程創建、銷燬。如newCachedThreadPool臨時創建線程

線程池的工作線程被抽象爲靜態內部類 Worker,private final HashSet<Worker> workers = new HashSet<>();基於AQS實現。

3)ThreadFactory 提供創建線程邏輯。

4)任務提交時被拒絕,如線程池已經處於 SHUTDOWN 狀態,Java 標準庫提供了類似ThreadPoolExecutor.AbortPolicy等默認實現,也可以按照需求自定義。

以上構造體現在構造參數上:

1)corePoolSize,核心線程數,可以理解爲長期駐留的線程數目,newFixedThreadPool 會將其設置爲 nThreads,而對於 newCachedThreadPool 則是爲 0。

2)maximumPoolSize,線程不夠時能夠創建的最大線程數,newFixedThreadPool,當然就是 nThreads,因爲其要求是固定大小,而 newCachedThreadPool 則是 Integer.MAX_VALUE。

3)keepAliveTime 和 TimeUnit,指定額外的線程能夠閒置多久,有些線程池不需要。

4)workQueue,工作隊列,必須是 BlockingQueue。(BlockQueue存入任務隊列時是沒有阻塞,使用的是offer,無阻塞添加方法。BlockQueue取出任務隊列時是有阻塞,有超時使用poll取值,無超時使用take阻塞方法取值)

線程池生命週期流程圖:(Idle:閒置的懶散的)

Execute源碼:


public void execute(Runnable command) {
…
  int c = ctl.get();
// 檢查工作線程數目,低於corePoolSize則添加Worker
  if (workerCountOf(c) < corePoolSize) {
      if (addWorker(command, true))
          return;
      c = ctl.get();
  }
// isRunning就是檢查線程池是否被shutdown
// 工作隊列可能是有界的,offer是比較友好的入隊方式
  if (isRunning(c) && workQueue.offer(command)) {
      int recheck = ctl.get();
// 再次進行防禦性檢查
      if (! isRunning(recheck) && remove(command))
          reject(command);
      else if (workerCountOf(recheck) == 0)
          addWorker(null, false);
  }
// 嘗試添加一個worker,如果失敗意味着已經飽和或者被shutdown了
  else if (!addWorker(command, false))
      reject(command);
}

如何選擇線程池:

1)避免任務堆積: newFixedThreadPool 是創建指定數目的線程,但是其工作隊列是無界的,如果工作線程數目太少,導致處理跟不上入隊的速度,這就很有可能佔用大量系統內存,出現 OOM。診斷時,你可以使用 jmap 之類的工具,查看是否有大量的任務對象入隊。

2)避免過度擴展線程:通常在處理大量短時任務時,使用緩存的線程池

3)線程泄漏:這種情況往往是因爲任務邏輯有問題,導致工作線程遲遲不能被釋放。(放在線程池中的線程要捕獲異常,如果直接拋出異常,每次都會創建線程,也就等於線程池沒有發揮作用,如果大併發下一直創建線程可能會導致JVM掛掉)

4)避免死鎖

5)儘量避免在使用線程池時操作 ThreadLocal

6)線程數設置:如果主要都是計算等消耗CPU的任務則設置成N或者N+1,如果是IO問題則參考:
線程數 = CPU核數 × 目標CPU利用率 ×(1 + 平均等待時間/平均工作時間),時間並不能精準預計,需要根據採樣或者概要分析等方式進行計。

7)系統資源限制,如果線程調用的資源是瓶頸則需擴展資源,如端口個數限制等

 

擴展AQS:J.U.C是基於AQS實現的,AQS是一個同步器,設計模式是模板模式。核心數據結構:雙向鏈表 + state(鎖狀態),底層操作:CAS。流程是

AQS 內部數據和方法:

一個 volatile 的整數成員表徵狀態,同時提供了 setState 和 getState 方法

一個先入先出(FIFO)的等待線程隊列,以實現多線程間競爭和等待,這是 AQS 機制的核心之一。

各種基於 CAS 的基礎操作方法,以及各種期望具體同步結構去實現的 acquire/release 方法。

ReentrantLock 爲例,它內部通過擴展 AQS 實現了 Sync 類型,以 AQS 的 state 來反映鎖的持有情況,線程間競爭則是 AQS 通過 Waiter 隊列與 acquireQueued 提供的,cquireQueued 的邏輯,簡要來說,就是如果當前節點的前面是頭節點,則試圖獲取鎖,一切順利則成爲新的頭節點;否則,有必要則等待

1、synchronized在JVM中是會進行鎖升級和降級的,並且是基於CAS來掌握競爭的情況,在競爭不多的情況下利用CAS的輕量級操作來減少開銷。
2、而AQS也是基於CAS操作隊列的,位於隊列頭的節點優先獲得鎖,其他的節點會被LockSupport.park()起來(這個好像依賴的是操作系統的互斥鎖,應該也是個重量級操作)。
我覺得這兩種方式都是基於CAS操作的,只是操作的對象不同(一個是Mark Word,一個是隊列節點),當競爭較多時,還是不可避免地會使用到操作系統的互斥鎖。然而,我再測試這兩者的性能時,在無競爭的情況下,兩者性能相當,但是,當競爭起來後,AQS的性能明顯比synchronized要好

AQS對線程的掛起喚醒是通過locksupport實現的,而wait基於monitor;一般用併發庫就不用Object.wait、notify之類了

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章