Java Executor 框架學習總結

renchx發表於2015-11-06

大多數併發都是通過任務執行的方式來實現的。一般有兩種方式執行任務:序列和並行。

class SingleThreadWebServer {
  public static void main(String[] args) throws Exception {
    ServerSocket socket = new ServerSocket(80);
    while(true) {
      Socket conn = socket.accept();
      handleRequest(conn);
    }
  }
}
class ThreadPerTaskWebServer {
  public static void main(String[] args) throws Exception {
    ServerSocket socket = new ServerSocket(80);
    while(true) {
      final Socket conn = socket.accept();
      Runnable task = new Runnable() {
        public void run() {
          handleRequest(conn);
        }
      };
      new Thread(task).start();
    }
  }
}

當然上面的這兩種方式都是有問題的。單執行緒的問題就是併發量會是瓶頸,多執行緒版本就是無限制的建立執行緒會導致資源不足問題。

Executor 框架

任務是一組邏輯工作單元,而執行緒是使任務非同步執行的機制。

JDK 提供了 Executor 介面:

public interface Executor {
    void execute(Runnable command);
}

雖然 Executor 介面比較簡單,但是卻是非同步任務執行框架的基礎,該框架能支援多種不同型別的任務執行策略。它提供了一種標準的方式把任務的提交過程與執行過程進行了解耦。用 Runnable 來代表任務。Executor 的實現提供了對生命週期的支援以及統計資訊應用程式管理等機制。

Executor 是基於生產者消費者模式的,提交任務的操作相當於生產者,執行任務的執行緒相當於消費。

基於 Executor 的 WebServer 例子如下:

public class TaskExecutorWebServer {
  private static final int NTHREADS = 100;
  private static final Executor exec = Executors.newFixedThreadPool(NTHREADS);
  public static void main(String[] args) throws Exception {
    ServerSocket serverSocket = new ServerSocket(80);
    while (true) {
      final Socket conn = serverSocket.accept();
      Runnable task = new Runnable() {
        @Override
        public void run() {
          handleRequest(conn);
        }
      };
      exec.execute(task);
    }
  }
}

另外可以自己實現 Executor 來控制是併發還是並行的,如下面程式碼:

/**
 * 執行已提交的 Runnable 任務的物件。
 * 此介面提供一種將任務提交與每個任務將如何執行的機制(包括執行緒使用的細節、排程等)分離開來的方法。
 * 通常使用 Executor 而不是顯式地建立執行緒。
 *
 *
 * @author renchunxiao
 *
 */
public class ExecutorDemo {
  public static void main(String[] args) {
    Executor executor = new ThreadExecutor();
    executor.execute(new Runnable() {
      @Override
      public void run() {
        // do something
      }
    });
    Executor executor2 = new SerialExecutor();
    executor2.execute(new Runnable() {
      @Override
      public void run() {
        // do something
      }
    });
  }
}
/**
 * 建立一個執行緒來執行 command
 *
 * @author renchunxiao
 *
 */
class ThreadExecutor implements Executor {
  @Override
  public void execute(Runnable command) {
    new Thread(command).start();
  }
}
/**
 * 序列執行 command
 *
 * @author renchunxiao
 *
 */
class SerialExecutor implements Executor {
  @Override
  public void execute(Runnable command) {
    command.run();
  }
}

執行緒池

執行緒池就是執行緒的資源池,可以通過 Executors 中的靜態工廠方法來建立執行緒池。

  • newFixedThreadPool。建立固定長度的執行緒池,每次提交任務建立一個執行緒,直到達到執行緒池的最大數量,執行緒池的大小不再變化。
  • newSingleThreadExecutor。單個執行緒池。
  • newCachedThreadPool。根據任務規模變動的執行緒池。
  • newScheduledThreadPool。建立固定長度的執行緒池,以延遲或定時的方式來執行任務。

JVM 只有在所有非守護執行緒全部終止後才會退出,所以,如果無法正確的關閉 Executor,那麼 JVM 就無法結束。

為了解決執行服務的生命週期問題,有個擴充套件 Executor 介面的新介面 ExecutorService。

public interface ExecutorService extends Executor {
  void shutdown();
  List<Runnable> shutdownNow();
  boolean isShutdown();
  boolean isTerminated();
  boolean awaitTermination(long timeout, TimeUnit unit)
    throws InterruptedException;
  <T> Future<T> submit(Callable<T> task);
  <T> Future<T> submit(Runnable task, T result);
  Future<?> submit(Runnable task);
  <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
    throws InterruptedException;
  <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                  long timeout, TimeUnit unit)
    throws InterruptedException;
  <T> T invokeAny(Collection<? extends Callable<T>> tasks)
    throws InterruptedException, ExecutionException;
  <T> T invokeAny(Collection<? extends Callable<T>> tasks,
          long timeout, TimeUnit unit)
    throws InterruptedException, ExecutionException, TimeoutException;
}

ExecutorService 生命週期有三種狀態:執行、關閉、已終止。ExecutorService 在初始建立時處於執行狀態。shutdown 方法會平緩關閉:不在接受新的任務,並且等待已經執行的任務執行完成(包括那些還未開始的任務)。shutdownNow 方法將粗暴關閉:它將嘗試取消所有執行中的任務,並且不再啟動佇列中尚未開始的任務。所有任務都執行完成後進入到已終止狀態。

Callable 和 Future

Executor 框架使用 Runnable 作為基本的任務表示形式。Runnable 是一種有侷限性的抽象,它的 run 方法不能返回值和丟擲一個受檢查異常。

許多工實際上是存在延時的計算,例如資料庫查詢,從網路獲取資源。對於這些任務,Callable 是更好的抽象,它認為 call 將返回一個值,並且可能丟擲異常。

Executor 執行的任務有四個生命週期階段:建立、提交、開始和完成。由於有些任務需要很長時間有可能希望取消,在 Executor 框架當中,已提交未開始的任務可以取消。

Future 表示一個任務的生命週期,並且提供了相應的方法來判斷是否已經完成或取消,以及獲取任務的結果和取消任務等。

相關文章