Java併發—Executor框架

Executor框架

在Java中, 使用線程來異步執行任務. Java線程的創建與銷燬需要一定的開銷, 如果我們爲每一個任務創建一個新線程來執行, 這些線程的創建與銷燬將消耗大量的計算資源. 同時, 爲每一個任務創建一個新線程來執行, 這種策略可能會使處於高負荷的應用最終崩潰.

1.Executor框架簡介

1>Executor框架的兩級調度模型

在HotSpot VM的線程模型中, Java線程被一對一映射爲本地操作系統線程(輕量級進程LWP). Java線程啓動時會創建一個本地操作系統線程. 當該Java線程終止時, 這個操作系統線程也會被回收. 操作系統會調度所有線程並將他們分配給可用的CPU.

在上層, Java多線程程序通常把應用分解爲若干個任務, 然後使用用戶級的調度器(Executor框架)將這些任務映射爲固定數量的線程; 在底層, 操作系統內核將這些線程映射到硬件處理器上. 應用通過Executor框架控制上層的調度; 而下層的調度由操作系統內核控制, 下層的調度不應受應用程序控制

在這裏插入圖片描述

2>Executor框架的結構與成員

Executor框架可分爲兩部分 : Executor的結構和Executor框架包含的成員組件

1.Executor框架的結構

Executor框架主要由3大部分組成如下 :

  • 任務 : 包括被執行任務需要實現的接口 : Runnable接口或Callable接口
  • 任務的執行 : 包括任務執行機制的核心接口Executor, 以及繼承自Executor的ExecutorService接口與.Executor有兩個關鍵類實現了ExecutorService接口(ThreadPoolExecutor和ScheduledThreadExecutor)
  • 異步計算的結果 : 包括接口Future和實現Future接口的FutureTask類

下面是一些類和接口的簡介 :

  • Executor是一個接口, 它是Executor框架的基礎, 它將任務的提交與任務的執行分離開來
  • ThreadPoolExecutor是線程池的核心實現類, 用來執行被提交的任務
  • ScheduledThreadPoolExecutor是一個實現類, 可以在給定的延遲後運行命令, 或者定期執行命令. ScheduledThreadPoolExecutor比Timer更靈活, 功能更強大
  • Future接口和實現Future接口的FutureTask類, 代表異步計算的結果
  • Runnable接口Callable接口的實現類, 都可以被ThreadPoolExecutor和ScheduledThreadPoolExecutor執行

在這裏插入圖片描述

主線程首先要創建Runnable或者Callable接口的任務對象. 工具類Executors可以把一個Runnable對象封裝爲一個Callable對象(Executors.callable(Runnable task))或Executors.callable(Runnable task, Object result))

然後可以把Runnable對象直接交給ExecutorService執行(ExecutorService.execute(Runnable command)); 或者也可以把Runnable對象或Callable對象提交給ExecutorService執行(ExecutorService.submit(Runnable task) 或ExecutorService.submit(Callable< T> task))

如果執行ExecutorService.submit(…), ExecutorService將返回一個實現Future接口的對象(現在的版本是FutureTask對象).

2.Executor框架的成員
  • ThreadPoolExecutor通常使用工廠類Executors來創建, Executors可以創建三種類型的ThreadPoolExecutor : SingleThreadExecutor, FixedThreadPool, CachedThreadPool

    • FixedThreadPool : 使用固定線程數的線程池, 適用於爲了滿足資源管理的需求, 而需要限制當前線程數量的應用場景, 適用於負載比較重的服務器
    • SingleThreadExecutor : 使用單個線程的SingleThreadExecutor. 適用於需要保證順序地執行各個任務, 並且在任意時間不會有多個線程是活動的應用場景
    • CachedThreadPool : 大小無界的線程池, 適用於很多短期異步任務的小程序, 或者是負載較清的服務器
  • ScheduledThreadPoolExecutor通常使用Executors工廠類來創建, Executors可以創建兩種類型的ScheduledThreadPoolExecutor

    • ScheduledThreadPoolExecutor : 包含若干線程的ScheduledThreadPoolExecutor, 適用於需要多個後臺線程執行的週期任務
    • SingleScheduledThreadPoolExecutor : 只包含一個線程的ScheduledThreadPoolExecutor, 適用於單個後臺線程執行週期任務
  • Future接口

    Future接口和實現Future接口的FutureTask類用來表示異步計算的結果. 當把Runnable接口或Callable接口的實現類提交(submit)給ThreadPoolExecutor時, 會返回一個FutureTask對象

  • Runnable接口和Callable接口

    Runnable接口和Callable接口的實現類, 都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor執行, 它們之間的區別是Runnable不會返回結果, 而Callable可以返回結果

2.ThreadPoolExecutor詳解

Executor框架最核心的類是ThreadPoolExecutor, 它是線程池的實現類, 主要由下面4個組件構成

  • corePool : 核心線程池的大小
  • maximum : 最大線程池的大小
  • BlockingQueue : 用來暫時保存任務的工作隊列
  • RejectedExecutionHandler : 當ThreadPoolExecutor已經關閉或ThreadPoolExecutor已經飽和(達到了最大線程池大小且工作隊列已滿), execute()方法將要調用的Handler

通過Executor框架的工具類Executors, 可以創建3種類型的ThreadPoolExecutor

  • FixedThreadPool
  • SingleThreadExecutor
  • CachedThreadPool

1>FixedThreadPool

FixedThreadPool被稱爲可重用固定線程數的線程池. FixedThreadPool的corePoolSize和maximumPoolSize都被設置爲創建FixedThreadPool時指定的參數nThreads; 當線程池中的線程數大於corePoolSize時, keepAliveTIme爲多於空閒線程等待新任務的最長時間, 超過這個時間後多餘的線程將被終止. 把keepAliveTime設置爲0L就意味着多餘的空閒線程會被立即終止

在這裏插入圖片描述

  • 1.如果當前運行的線程數少於corePoolSize, 則創建新線程來執行任務
  • 2.在線程池完成預熱之後(當前運行的線程數等於corePoolSize), 將任務加入LinkedBlockingQueue
  • 3.線程執行完1中的任務後, 會在循環反覆從LinkedBlockingQueue獲取任務來執行

FixedThreadPool使用無界隊列LinkedBlocking作爲線程池的工作隊列(隊列的容量爲Integer.MAX_VALUE). 使用無界隊列作爲工作隊列會對線程帶來如下區別.

  • 線程池中的線程數量不會超過corePoolSize
  • maximumPoolSize是一個無效參數
  • keepAliveTime是一個無效參數
  • 運行中的FixedThreadPool不會拒絕任務

2>SingleThreadExecutor

SingleThreadExecutor是使用單個worker線程的Executor. SingleThreadExecutor的corePoolSize和maximum參數被設置爲1. 其他參數與FixedThreadPool相同. SingleThreadExecutor使用無界隊列LinkedBlockingQueue作爲線程池的工作隊列

在這裏插入圖片描述

  • 如果當前線程數小於corePoolSize(即線程池中無運行的線程, 則創建一個新線程來執行任務)
  • 在線程池完成預熱之後(當前線程池中會有一個運行的線程), 將任務加入LinkedBlockingQueue
  • 線程執行完1中的任務後, 會在一個無限循環中反覆從LinkedBlockingQueue中獲取任務來執行

3>CachedThreadPool

CachedThreadPool是一個會根據需要創建新線程的線程池. CachedThreadPool的corePoolSize被設置爲0, 即corePool爲空; maximumPoolSize被設置爲Integer.MAX_VALUE, 即maximumPool是無界的. 這裏把keepAliveTime設置爲60L, 也就是CachedThreadPool中的空閒線程等待新任務的最長時間爲60秒. 空閒線程超過60秒後會被終止

FixedThreadPool和SingleThreadExecutor使用無界隊列LinkedBlockingQueue作爲現場的工作隊列. CachedThreadPool使用沒有容量的SynchronousQueue作爲線程池的工作隊列, 但CachedThreadPool中的maximumPool是無界的, 也就是說如果主線程提交任務的速度高於maximumPool中線程處理任務的速度時, CachedThreadPool會不斷創建新線程. 極端情況下, CachedThreadPool會因爲創建過多線程而耗盡CPU和內存資源

在這裏插入圖片描述

  • 1.首先執行SynchronousQueue.offer(Runnable task). 如果當前maximumPool中有空閒線程在執行SynchronousQueue.poll(keepAliveTime, TImeUnit.NANOSECONDS), 那麼主線程執行的offer操作與空閒線程執行的poll操作配對成功
  • 2.當初始maximumPool爲空, 或者maximum中當前沒有空閒線程時, 將沒有線程執行SynchronousQueue.poll(keeyAliveTime, TImeUnit.NANOSECONDS). 這種情況下, 步驟1將失敗, 此時CachedThreadPool會創建一個新線程執行任務, execute()方法執行完成
  • 3.在步驟2中新創建的線程將任務執行完之後, 會執行SynchronousQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS). 這個poll操縱會讓空閒線程最多在SynchronousQueue中等待60秒中. 如果60秒內主線程提交了一個新任務(主線程執行步驟1),那麼這個空閒線程將執行主線程提交的新任務; 否則, 這個空閒線程將被終止.因此長時間保持空閒的CachedThreadPool不會使用任何資源

SynchronousQueue是一個沒有容量的阻塞隊列. 每個插入操作必須等待另一個線程對應的移除操作. CachedThreadPool使用SynchronousQueue, 把主線程提交的任務傳遞給空閒線程執行.

在這裏插入圖片描述

3.ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor繼承自ThreadPoolExecutor. 它主要用來在給定的延遲之後運行任務, 或者定期執行任務.ScheduledThreadPoolExecutor的功能與TImer類似, 但ScheduledThreadPoolExecutor功能更強大, 更靈活. TImer對應的是單個後臺線程, 而ScheduledThreadPoolExecutor可以在構造函數中指定多個對應的後臺線程數

1>ScheduledThreadPoolExecutor運行機制

ScheduledThreadPoolExecutor的執行示意圖如圖

在這裏插入圖片描述
DelayQueue是一個無界隊列, 所以ThreadPoolExecutor的maximumPoolSize在ScheduledThreadPoolExecutor中沒有什麼意義.

ScheduledThreadPoolExecutor的執行主要分爲兩大部分

  • 當調用ScheduledThreadPoolExecutor的scheduleAtFixedRate()方法或者添加scheduleWithFixedDelay方法時, 會向ScheduledThreadPoolExecutor的DelayQueue添加一個實現了RunnableScheduledFutur接口的ScheduledFutureTask
  • 線程池中的線程從DelayQueue中獲取ScheduledFutureTask, 然後執行任務

2>ScheduledThreadPoolExecutor的實現

ScheduledThreadPoolExecutor會把待調度的任務(ScheduledFutureTask)放到一個DelayQueue中, ScheduledFutureTask主要包含3個成員變量, 如下 :

  • long型成員變量time, 表示這個任務將要被執行的具體時間
  • long型成員變量sequenceNumber, 表示這個任務被添加到ScheduledThreadPoolExecutor中的序號
  • long型成員變量period,表示任務執行的間隔週期

DelayQueue封裝了一個PriorityQueue, 這個PriorityQueue會對隊列中的ScheduledFutureTask進行排序. 排序時, time小的排在前面(時間早的任務先被執行). 如果兩個ScheduledFutureTask的time相同, 就比較sequenceNumber, sequenceNumber小的排在前面(先提交的任務先執行)

在這裏插入圖片描述

  • 1.線程1從DelayQueue中獲取到已到期的ScheduledFutureTask(DelayQueue.take()). 到期任務是指ScheduledFutureTask的time大於等於當前時間
  • 2.線程1執行這個ScheduledFutureTask
  • 3.線程1修改ScheduledFutureTask的time變量爲下次將要執行的時間
  • 4.線程1把這個修改time之後的ScheduledFutureTask放回DelayQueue中(DelayQueue.add())

4.FutureTask

1>FutureTask簡介

FutureTask除了實現Future接口之外, 還實現了Runnable接口. 因此, FutureTask可以交給Executor執行, 也可以由調用線程直接執行.根據FutureTask.run()方法被執行的時機, FutureTask可以處於下面3種狀態

  • 1.未啓動, FutureTask.run()方法還沒有執行之前, FutureTask處於未啓動狀態. 當創建一個FutureTask, 且沒有執行FutureTask.run()方法之前, 這個FutureTask處於未啓動狀態
  • 2.已啓動, FutureTask.run()方法被執行的過程中, FutureTask處於已啓動狀態.
  • 3.已完成, FutureTask.run()方法執行完後正常結束, 或被取消(FutureTask.cancel(…)), 或執行FutureTask.run()方法時拋出異常而異常結束, FutureTask處於已完成狀態

當FutureTask處於未啓動或已啓動狀態時, 執行FutureTask.get()方法將導致調用線程阻塞.當FutureTask處於已完成狀態時, 執行FutureTask.get()方法將導致調用線程立即返回結果或拋出異常

2>FutureTask的使用

可以把FutureTask交給Executor執行; 也可以通過ExecutorService.submit(…)方法返回一個FutureTask, 然後執行FutureTask.get()或FutureTask.cancel()方法. 還可以單獨使用FutureTask

在這裏插入圖片描述

3>FutureTask的實現

FutureTask的實現是基於AbstractQueuedSynchronizer(AQS). J.U.C中很多可阻塞類都是基於AQS實現的. AQS是一個同步框架, 它提供通用原子性管理同步狀態, 阻塞和喚醒線程, 以及維護被阻塞線程的隊列.

每個基於AQS實現的同步器都會包含兩種類型的操作 :

  • 至少一個acquire操作. 這個操作阻塞調用線程, 除非/直到AQS的狀態允許這個線程繼續執行.
  • 至少一個release操作. 這個操作改變AQS的狀態, 改變後的狀態可允許一個或多個阻塞線程被接觸阻塞

FutureTask聲明瞭一個內部私有的繼承於AQS的內部子類Sync, 這個內部子類只用實現狀態檢查和狀態更新的方法即可, 這些方法將控制FutureTask的獲取和釋放操作. Sync實現了AQS的tryAcquireShared(int)方法和tryReleaseShared(int)方法, Sync通過這兩個方法來檢查和更新同步狀態

在這裏插入圖片描述

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