番外1、多線程

Executor

Module java.base
Package java.util.concurrent
Interface Executor

  執行已提交的可運行任務的對象。該接口提供了一種將任務提交與如何運行每個任務的機制(包括線程使用、調度等細節)分離的方法。通常使用Executor而不是顯式地創建線程。
  例如,相比一組任務中的每個任務調用new Thread(new RunnableTask()).start() ,採用如下的方法更好:

 Executor executor = anExecutor();
 executor.execute(new RunnableTask1());
 executor.execute(new RunnableTask2());
 ...

  但是,Executor接口並不嚴格要求執行是異步的。在最簡單的情況下,executor可以立即在調用者的線程中運行提交的任務:

class DirectExecutor implements Executor {
   public void execute(Runnable r) {
     r.run();
   }
 }

  更典型的情況是,任務在調用者的線程之外的某個線程中執行。下面的執行程序爲每個任務生成一個新線程。

class ThreadPerTaskExecutor implements Executor {
   public void execute(Runnable r) {
     new Thread(r).start();
   }
 }

  許多Executor實現對如何以及何時調度任務施加了某種限制。下面的執行程序將任務的提交序列化到第二個執行程序,演示了複合執行程序。

class SerialExecutor implements Executor {
   final Queue<Runnable> tasks = new ArrayDeque<>();
   final Executor executor;
   Runnable active;

   SerialExecutor(Executor executor) {
     this.executor = executor;
   }

   public synchronized void execute(Runnable r) {
     tasks.add(() -> {
       try {
         r.run();
       } finally {
         scheduleNext();
       }
     });
     if (active == null) {
       scheduleNext();
     }
   }

   protected synchronized void scheduleNext() {
     if ((active = tasks.poll()) != null) {
       executor.execute(active);
     }
   }
 }

此包中提供的Executor實現了ExecutorService,這是一個用處更加廣泛的接口。

ThreadPoolExecutor類提供可擴展的線程池實現。
Executors 類爲這些executor提供了方便的工廠方法。

ExecutorService

public interface ExecutorService extends Executor

  ExecutorService是一個Executor, 它提供用於管理終止,以及可以產生用於跟蹤一個或多個異步任務進展的Future的方法。ExecutorService可以被關閉,這將導致它拒絕新任務。提供了兩種不同的方法來關閉ExecutorService。shutdown()方法允許在終止之前執行以前提交的任務,而shutdownNow()方法可以防止等待的任務啓動並嘗試停止當前正在執行的任務。在終止時,執行程序沒有正在執行的任務,沒有等待執行的任務,也不能提交新的任務。

  方法submit擴展了基本方法Executor.execute(Runnable),創建並返回一個可用於取消執行和/或等待完成的Future。
  方法invokeAny和invokeAll執行最常用的批量執行形式,執行一組任務,然後等待至少一個或全部任務完成。(類ExecutorCompletionService可用於編寫這些方法的定製變體。)

Executors 類爲這個包中提供的executor服務提供工廠方法。

使用案例:
下面是一個網絡服務的例子,其中線程池服務中的線程傳入請求。它使用預配置的Executors.newFixedThreadPool(int)工廠方法:

class NetworkService implements Runnable {
   private final ServerSocket serverSocket;
   private final ExecutorService pool;

   public NetworkService(int port, int poolSize)
       throws IOException {
     serverSocket = new ServerSocket(port);
     pool = Executors.newFixedThreadPool(poolSize);
   }

   public void run() { // run the service
     try {
       for (;;) {
         pool.execute(new Handler(serverSocket.accept()));
       }
     } catch (IOException ex) {
       pool.shutdown();
     }
   }
 }

下面的方法分兩個階段關閉ExecutorService,首先調用shutdown來拒絕傳入的任務,然後在必要時調用shutdownNow來取消任何延遲任務:

void shutdownAndAwaitTermination(ExecutorService pool) {
   pool.shutdown(); // Disable new tasks from being submitted
   try {
     // Wait a while for existing tasks to terminate
     if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
       pool.shutdownNow(); // Cancel currently executing tasks
       // Wait a while for tasks to respond to being cancelled
       if (!pool.awaitTermination(60, TimeUnit.SECONDS))
           System.err.println("Pool did not terminate");
     }
   } catch (InterruptedException ie) {
     // (Re-)Cancel if current thread also interrupted
     pool.shutdownNow();
     // Preserve interrupt status
     Thread.currentThread().interrupt();
   }
 }

AbstractExecutorService

public abstract class AbstractExecutorService
extends Object
implements ExecutorService

  提供ExecutorService執行方法的默認實現。這個類使用newTaskFor返回的RunnableFuture實現了submit、invokeAny和invokeAll方法,這個RunnableFuture默認是這個包中提供的FutureTask類。例如,submit(Runnable)的實現創建了一個執行並返回的關聯的RunnableFuture。子類可以覆蓋newTaskFor方法來返回RunnableFuture實現,而不是FutureTask。
  下面是一個類的示意圖,它定製ThreadPoolExecutor來使用CustomTask類,而不是默認的FutureTask:

public class CustomThreadPoolExecutor extends ThreadPoolExecutor {

   static class CustomTask<V> implements RunnableFuture<V> {...}

   protected <V> RunnableFuture<V> newTaskFor(Callable<V> c) {
       return new CustomTask<V>(c);
   }
   protected <V> RunnableFuture<V> newTaskFor(Runnable r, V v) {
       return new CustomTask<V>(r, v);
   }
   // ... add constructors, etc.
 }

ThreadPoolExecutor

public class ThreadPoolExecutor extends AbstractExecutorService

  使用可能的幾個池線程之一執行每個提交的任務的ExecutorService,通常使用exec工廠方法進行配置。

  線程池解決了兩個不同的問題:由於減少了每個任務的調用開銷,線程池通常在執行大量異步任務時提供了更好的性能,並且提供了一種綁定和管理資源(包括執行任務集合時消耗的線程)的方法。

  爲了在廣泛的上下文中有用,這個類提供了許多可調參數和可擴展性掛鉤。然而,程序員要求使用更方便的Executors 工廠方法Executors.newCachedThreadPool()(無界的線程池,線程自動回收),Executors.newFixedThreadPool (int)(固定大小的線程池)和Executors.newSingleThreadExecutor()(單個後臺線程),preconfigure設置爲最常見的使用場景。否則,在手動配置和調優該類時,請使用以下指南:

  1. Core and maximum pool sizes。
       ThreadPoolExecutor將根據corePoolSize(參見getCorePoolSize())和maximumPoolSize(參見getMaximumPoolSize())設置的界限自動調整池大小(參見getPoolSize())。在方法execute(Runnable)中提交新任務時,如果運行的線程小於corePoolSize,則創建一個新線程來處理請求,即使其他工作線程處於空閒狀態。
       否則,如果運行的線程小於maximumPoolSize,則只在隊列滿時創建一個新線程來處理請求。
       通過將corePoolSize和maximumPoolSize設置爲相同的值,您可以創建一個固定大小的線程池。通過將maximumPoolSize設置爲一個基本無界的值,如Integer.MAX_VALUE,允許池容納任意數量的併發任務。最典型的情況是,僅在構建時設置核心和最大池大小,但也可以使用setCorePoolSize(int)和setMaximumPoolSize(int)動態更改它們。

  2. 按需構建
      默認情況下,即使是核心線程也只是在新任務到達時才創建和啓動,但是可以使用方法prestartCoreThread()或prestartAllCoreThreads()動態地覆蓋它。如果使用非空隊列構造池,則可能需要預啓動線程。

  3. 創建新線程
      使用ThreadFactory創建新線程。如果沒有另外指定,將使用executols . defaultthreadfactory(),它將創建所有屬於相同ThreadGroup的線程,並且具有相同的NORM_PRIORITY優先級和非守護進程狀態。通過提供不同的ThreadFactory,您可以更改線程的名稱、線程組、優先級、守護進程狀態等。如果ThreadFactory通過從newThread返回null來創建一個線程失敗,執行程序將繼續執行,但可能無法執行任何任務。線程應該擁有“modifyThread”RuntimePermission。如果工作線程或使用池的其他線程不擁有此權限,服務可能會降級:配置更改可能不會及時生效,關閉池可能仍然處於可能終止但尚未完成的狀態。

  4. 保持存活時間
      如果當前池中有超過corePoolSize的線程,那麼如果空閒時間超過keepAliveTime(請參閱getKeepAliveTime(TimeUnit)),則多餘的線程將被終止。這提供了一種在池沒有被積極使用時減少資源消耗的方法。如果以後池變得更活躍,就會構造新的線程。還可以使用setKeepAliveTime(long, TimeUnit)方法動態更改此參數。使用Long.MAX_VALUE TimeUnit.NANOSECONDS有效地禁止空閒線程在關閉之前終止。默認情況下,keep-alive策略只適用於擁有多於corePoolSize線程的情況,但是也可以使用allowCoreThreadTimeOut(boolean)方法將這個超時策略應用於核心線程,只要keepAliveTime值不爲零。

  5. 隊列
    任何BlockingQueue都可以用來傳輸和保存提交的任務。此隊列的使用與池大小調整相互作用。

    • 如果運行的線程小於corePoolSize,則執行程序總是希望添加新線程而不是排隊
    • 如果正在運行的線程數目大於等於corePoolSize,執行程序總是希望對請求進行排隊,而不是添加新線程。
    • 如果一個請求不能排隊,那麼將創建一個新線程,除非這個線程的大小超過maximumPoolSize,在這種情況下,任務將被拒絕。

    排隊有三種基本策略:

    • 直接的傳遞。工作隊列的一個很好的默認選擇是SynchronousQueue,它將任務傳遞給線程,而不會佔用線程。在這裏,如果沒有立即可用的線程來運行任務,則對任務進行排隊的嘗試將失敗,因此將構造一個新線程。此策略在處理可能具有內部依賴項的請求集時避免鎖定。直接移交通常需要無界的最大池大小,以避免拒絕新提交的任務。反過來,當命令到達的平均速度比它們被處理的速度還要快時,就有可能出現無限的線程增長。
    • 無界隊列。當所有的corePoolSize線程都處於繁忙狀態時,使用無界隊列(例如沒有預定義容量的LinkedBlockingQueue)將導致新任務在隊列中等待。因此,創建的線程不會超過corePoolSize。(因此,maximumPoolSize的值沒有任何影響。)當每個任務完全獨立於其他任務時,這可能是合適的,因此任務不會影響其他任務的執行;例如,在web頁面服務器中。雖然這種類型的隊列在平滑短暫的請求突發方面很有用,但它也承認,當命令平均到達速度超過處理速度時,可能會出現無限的工作隊列增長。
    • 有界的隊列。有限的隊列(例如,ArrayBlockingQueue)在使用有限的最大池大小時有助於防止資源耗盡,但是調優和控制可能更困難。隊列大小和最大池大小可以相互交換:使用大隊列和小池可以最小化CPU使用、操作系統資源和上下文切換開銷,但是會導致人爲的低吞吐量。如果任務經常阻塞(例如,它們受到I/O的限制),系統可能會爲比其他方式允許的更多的線程安排時間。使用小隊列通常需要更大的池大小,這會使cpu更忙,但可能會遇到無法接受的調度開銷,這也會降低吞吐量。
  6. 拒絕接受任務
    在方法execute(Runnable)中提交的新任務將在執行程序關閉時被拒絕,並且在執行程序對最大線程和工作隊列容量使用有限的界限並達到飽和時也會被拒絕。在這兩種情況下,execute方法都會調用RejectedExecutionHandler.rejectedExecution(Runnable, ThreadPoolExecutor)方法的RejectedExecutionHandler。提供四個預定義的處理程序策略:

    • 在默認的ThreadPoolExecutor.AbortPolicy中。處理程序在拒絕時拋出運行時RejectedExecutionException異常。
    • 在 ThreadPoolExecutor.CallerRunsPolicy中。調用execute本身的線程將運行該任務。這提供了一個簡單的反饋控制機制,可以降低新任務的提交速度。
    • 在ThreadPoolExecutor.DiscardPolicy中。無法執行的任務將被丟棄。
    • 在ThreadPoolExecutor.DiscardOldestPolicy中,如果執行器沒有關閉,工作隊列頭部的任務將被刪除,然後重新執行(可能再次失敗,導致重複執行)。
      可以定義和使用其他類型的RejectedExecutionHandler類。這樣做需要謹慎,特別是當策略被設計爲僅在特定容量或隊列策略下工作時。
  7. Hook methods
      該類提供protected的可覆蓋的beforeExecute(Thread, Runnable) 和afterExecute(Runnable, Throwable) 方法,這些方法在每個任務執行之前和之後調用。這些可以用來操作執行環境;例如,重新初始化threadlocal、收集統計信息或添加日誌條目。此外,可以覆蓋terminate()方法,以便在執行程序完全終止後執行需要執行的任何特殊處理。
      如果hook, callback, or BlockingQueue方法拋出異常,則內部工作線程可能會失敗、突然終止並可能被替換。

  8. 隊列的維護
    方法getQueue()允許訪問工作隊列,以便進行監視和調試。強烈反對將此方法用於任何其他目的。提供的兩個方法remove(Runnable)和purge()可用於在大量排隊的任務被取消時幫助回收存儲。

  9. 回收
    不再在程序中引用並且沒有剩餘線程的池可以被回收(垃圾回收),而不需要顯式關閉。您可以配置一個池,通過設置適當的keep-alive時間(使用0個核心線程的下限)和/或設置allowCoreThreadTimeOut(布爾值)來允許所有未使用的線程最終死亡。

擴展的例子。該類的大多數擴展都會覆蓋一個或多個受保護的鉤子方法。例如,這裏有一個子類,添加了一個簡單的暫停/恢復功能:

class PausableThreadPoolExecutor extends ThreadPoolExecutor {
   private boolean isPaused;
   private ReentrantLock pauseLock = new ReentrantLock();
   private Condition unpaused = pauseLock.newCondition();

   public PausableThreadPoolExecutor(...) { super(...); }

   protected void beforeExecute(Thread t, Runnable r) {
     super.beforeExecute(t, r);
     pauseLock.lock();
     try {
       while (isPaused) unpaused.await();
     } catch (InterruptedException ie) {
       t.interrupt();
     } finally {
       pauseLock.unlock();
     }
   }

   public void pause() {
     pauseLock.lock();
     try {
       isPaused = true;
     } finally {
       pauseLock.unlock();
     }
   }

   public void resume() {
     pauseLock.lock();
     try {
       isPaused = false;
       unpaused.signalAll();
     } finally {
       pauseLock.unlock();
     }
   }
 }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章