Java基礎 異步任務與Executor框架

1. 引言

同步任務的發起和執行是在同一條時間線上進行的,往往以爲的阻塞,而異步任務的發起和執行在不同的時間線上。但是阻塞/非阻塞與同步/異步執行方式並沒有完全的對應關係,同步/異步的說法也是相對的,他取決於任務的執行方式以及我們的觀察角度。

2. Java Executor框架

2.1 Runnable、Callable接口

Runnable和Callable接口都是對任務處理邏輯對抽象,使得我們無需關注任務的具體處理邏輯。

//沒有返回狀態
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

//有返回狀態
@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}

2.2 Executor接口

Executor接口使得任務的提交能夠與任務執行的具體細節解耦,能夠給我們帶來信息隱藏和關注點分離的好處。

public interface Executor {

    /**
     * 傳入的Runnable實例將被執行,具體是在一個新的線程,或者線程池,
     * 或者與被調用者在同一線程,取決於Executor接口的具體實現
     */
    void execute(Runnable command);
}

Executor接口我們可以發現兩個問題:

  • 無法將任務處理結果返回給客戶端
  • 沒有定義釋放資源的方法

2.3 ExecutorService接口

ExecutorService接口繼承自Executor接口,ThreadPoolExecutor是其默認實現,相對於上面提到的兩點問題:

  • 其定義了幾個submit方法,這些方法能夠接受Callable接口或者Runnable接口表示的任務,並返回相應的Future實例,從而使客戶端在提交任務後可以獲取任務執行結果
  • 定義了shutdown()方法和shutdownNow()方法來釋放資源

下面列舉了一部分ExecutorService接口定義的方法

public interface ExecutorService extends Executor {

    // 提交一個callable實例,可以通過返回的future對象獲取到返回結果
    <T> Future<T> submit(Callable<T> task);

    // 提交一個runnabel實例,執行成功返回的future get()方法返回null
    Future<?> submit(Runnable task);

    //阻塞方法,提交callable實例集合,當第一個執行成功後便返回結果,並終止其餘線程執行
    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;

    // 阻塞方法,提交callable實例集合,當所有執行成功後返回future實例集合
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;

    // 正在執行的任務會繼續執行下去,沒有被執行的則中斷
    void shutdown();

    // 正在執行的任務停止,返回沒被執行的任務
    List<Runnable> shutdownNow();

    boolean isShutdown();

    boolean isTerminated();
}

2.4 Executors實用工具類

Excecutors能夠返回默認線程工廠,將Runnable實例轉爲Callable實例,並提供了一些能夠返回ExecutorService實例的快捷方法。

方法 說明
public static ExecutorService newCachedThreadPool() 適用於執行大量耗時較短且提交比較頻繁的任務。
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) 同上
public static ExecutorService newFixedThreadPool(int nThreads) 線程池大小等於最大線程池大小,該線程池中的工作者線程一般不會超時
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) 同上
public static ExecutorService newSingleThreadExecutor() 適合單/多生產者——單消費者模式
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) 同上
  • newCachedThreadPool()
    相當於new ThreadPoolExecutor(0, Integer.MAX_VALUE,
    60L, TimeUnit.SECONDS,
    new SynchronousQueue());
    核心線程池大小爲0,最大線程池大小不受限,工作者線程允許的最大空閒時間爲60s,內部以SynchronousQueue爲工作隊列,但是SynchronousQueue內部並不維護用於存儲隊列元素的實際存儲空間。在極端情況下,給該線程池每提交一個任務都會導致一個新的工作者線程被創建,因此適合適用於執行大量耗時較短且提交比較頻繁的任務。
  • public static ExecutorService newFixedThreadPool(int nThreads)
    相當於new ThreadPoolExecutor(nThreads, nThreads,
    0L, TimeUnit.MILLISECONDS,
    new LinkedBlockingQueue());
    核心線程池大小與最大線程池大小均爲nThreads,線程池中的空閒工作者線程不會被自動清理,注意使用完後需要主動關閉。

2.5 Future與FutureTask

無論是Runnable實例還是Callable實例所表示的任務,只要我們將其提交給線程池執行,那麼這些任務就是異步任務。

  • Runnable實例的優點是可以使用單獨的工作線程執行,缺點是沒有返回值
  • Callable實例的優點是有返回值,但是缺點是隻能用線程池執行,靈活性較低。

首先最基本的接口是Future,他代表了異步任務執行之後的返回信息

public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning);

    boolean isCancelled();

    boolean isDone();

    //阻塞方法,等任務執行完成後返回結果
    V get() throws InterruptedException, executionException;

    V get(long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException;

}

FutureTask是Future接口的基本實現類,他融合了Runnable接口(繼承)和Callable接口(組合)的優點,他的構造器可以將Callable實例轉換爲Runnable實例,同時也可以將FutureTask實例交給Executor執行。

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