第九章 java異步編程--《java多線程編程實戰指南-核心篇》

java.util.concurrent.Executor接口是對任務執行進行的抽象,Executor接口使得任務的提交方只需要知道它調用Executor.execute方法便可以使指定任務被執行,無需關係任務執行的細節,使得任務的提交能夠與任務執行的具體細節解耦,它在一定程度上能夠屏蔽任務同步執行與異步執行的差異,一定程度上縮小了同步編程與異步編程的代碼編寫方式。

工具類Executors

Executors能夠返回默認的線程工廠、將Runnable實例轉換爲Callable實例、能夠返回ExecutorServer實例的快捷方法:

  • Executors.newCahedThreadPool()。該方法的返回值相當於:
    new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>()),即核心線程池大小爲0,最大線程池大小不受限,工作者線程允許的最大空閒時間爲60秒,內部以SynchronousQueue爲工作隊列的一個線程池。這種配置意味着該線程池中的所有工作者線程在空閒了指定的時間後都可以被自動清理掉。由於該線程池的核心線程池大小爲0,因此提交給該線程執行的第一個任務會導致該線程池中的第一個工作者線程被創建並啓動。後序繼續給該線程繼續提交任務的時候,由於當前線程池大小已經超過核心線程池大小(0),因此ThreadPoolExecutor此時會將任務緩存到工作隊列之中。SynchronousQueue內部並不維護用於存儲隊列元素的實際存儲空間。一個生產者線程在執行SynchronousQueue.offer(E)的時候,如果沒有其他消費者線程因執行SynchronousQueue.take()而被暫停,那麼SynchronousQueue.off(E)調用會直接返回false,即入隊列失敗。因此,在該線程池中的所有工作者線程都在執行任務,即無空閒工作者線程的情況下給其提交任務會導致該任務無法被緩存成功。而ThreadPoolExecutor在任務緩存失敗且線程池當前大小未達到最大線程池大小(這裏的最大線程池大小實際上相當於不限)的情況下會創建並啓動新的工作者線程。在極端的情況下,給該線程每提交一個任務都會導致一個新的工作者線程被創建並啓動,而這最終會導致系統中的線程過多,從而導致過多的上下文切換而使整個系統被拖慢。因此,Executors.newCahedThreadPool()所返回的線程池適合於用來執行大量耗時短且提交頻率較高的任務,而提交頻率較高且耗時較長的任務(尤其是包含阻塞操作的任務)則不適合用Executors.newCahedThreadPool()所返回的線程池來執行。
  • Executors.newFixedThreadPool(int nThread)。該方法的返回值相當於:
    new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()),即一個無界隊列爲工作隊列,核心線程池大小與最大線程池大小均爲nThreads且線程池中的空閒工作者線程不會被自動清理的線程池,這是一種線程池大小一旦達到其核心線程池大小就既不會增加也不會減少工作者線程的固定大小的線程池。因此,這樣的線程池實例一旦不再需要,我們必須主動將其關閉。
  • Executors.newSIngleThreadExecutor。該方法的返回值相當於: 
    new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(),相當於Executors.newFixedThreadPool(1)所返回的線程池。不過,該線程池並非ThreadPoolExecutor實例,而是封裝了一個ThreadPoolExecutor實例且對外緊暴露ExecutorService接口鎖定義的方法的一個ExecutorService實例。該線程池便於我們實現單(多)生產者-單消費者模式。該線程池確保了在任意一個時刻只有一個任務會被執行,這就形成了類似鎖將原本併發的操作改爲串行的操作的效果。因此,該線程池適合用來執行訪問了非線程安全對象而我們又不希望因此引人鎖的任務。該線程池也適合用來執行I/O操作,因爲I/O操作往往受限於相當的I/O設備,使用多個線程執行同一種I/O操作可能並不會提高I/O效率,所以如果使用一個線程執行I/O足以滿足要求,那麼僅使用一個線程即可,這樣保障程序的簡單性以避免一些不必要的問題。

異步任務的批量執行:CompletionService

java.util.concurrent.CompletionService接口未異步任務的批量提交以及獲取這些任務的處理結果提供便利。

ExecutorCompletionService相當於Executor實例與BlockingQueue實例的一個融合體,ExecutorCompletionService每執行完一個異步任務,就將該任務對應的Future實例存入其內部維護的一個BlockingQueue實例之中,而其客戶端代碼則可以通過ExecutorCompletionService.take()調用來獲取這個FUture實例。

異步計算助手:FutureTask

異步任務中採用Runnable實例來表示異步任務,其優點是任務既可以交給一個專門的工作者線程執行,也可以交給一個線程池或者Executor的其他實現類來執行;其缺點是我們無法直接獲取任務的執行結果。使用Callable實例來表示異步任務,其優點是我們可以通過ThreadPoolExecutor.submit(Callable<T>)的返回值獲取任務的處理結果;其缺點是Callable實例表示的異步任務只能交給線程池執行,而無法直接交給一個專門的工作者線程或者Executor實現類來執行。

java.util.concurrent.FutureTask融合了Runnable和Callable接口的優點,即使Runnable接口的實現類也是Future接口的實現類:

計劃任務

java.util.concurrent.ScheduledExecutorService接口定義了一組方法用於執行計劃任務:

延遲執行提交任務:

public ScheduledFuture<?> schedule(Runnable command,
                                   long delay, TimeUnit unit);
public <V> ScheduledFuture<V> schedule(Callable<V> callable,
                                       long delay, TimeUnit unit);

週期性地執行提交任務:

public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                              long initialDelay,
                                              long period,
                                              TimeUnit unit)
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                 long initialDelay,
                                                 long delay,
                                                 TimeUnit unit)

 

 

發佈了35 篇原創文章 · 獲贊 3 · 訪問量 5968
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章