文章目錄
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執行。