內心豐富
拜託生活表面的相似
Future和Callable
callable的缺陷
-
沒有返回值
-
不能拋出checked Exception
原因?
這些方法都不是我們開發人員自己編寫的,就算可以往外面拋,也只能報異常,不能按照我們想要的規則進行處理。
Callable接口
- 類似於Runnable,被其他線程執行的任務
- 實現call方法
- 有返回值
Future類
Future是一個存儲類,它存儲call()這個任務的結果,而這個任務的執行時間是無法提前去確定的,因爲這完全取決於call()方法的執行。
我們可以通過Future.get來獲取Callable接口返回的執行結果,還可以通過Future.isDone()來判斷任務是否已經執行完畢,以及取消這個任務,限時獲取任務的結果等。
在call()未執行完畢之前,調用get()的線程(假定此時是主線程)會被阻塞知道call()方法返回結果後,此時future.get()纔會得到該結果,然後主線程纔會切換到Runnable狀態。
主要方法及具體實現
主要方法
可以看到 Future是一個接口,主要方法有5個。
- get()方法
get()方法的行爲取決於Callable任務的狀態,有5種情況。
- 任務正常完成—get()方法會立即返回結果。
- 任務尚未完成(還沒開始或者進行中)—get()將阻塞並直到任務完成。
- 任務執行過程中拋出異常Exception
get方法會拋出ExecutionException:這裏的拋出異常,是call()執行時產生的那個異常。不論call()執行是拋出的異常類型是什麼,最後get()拋出的異常類型都是ExecutionException。 - 任務被取消—get()方法會拋出CancellationException.
- 任務超時—get()有一個重載方法,是傳入一個延遲時間的。如果時間到了還沒有獲取到結果,get()方法就會拋出TimeoutException。
- get(long timeout,TimuUnit unit):有超時的獲取。
用get(long timeout,TimuUnit unit)方法時,如果call()在規定的時間內完成了任務,那麼就會正常獲取到返回值;而如果再指定時間內沒有計算出結果,那麼就會拋出TimeoutException。
超時不獲取,任務需取消。
- cancel方法
取消任務的執行。
- isDone方法
判斷線程是否執行完畢。
記住,是執行完畢,而不是執行成功,就算拋出異常,他也是執行完畢。
- isCancelled方法
判斷是否被取消
具體實現
- 線程池的submit方法返回Future對象
首先,我們要給線程池提交我們的任務,提交時線程池會立即返回給我們一個空的Future容器。當線程的任務一旦執行完畢,也就是當我們可以獲取結果的時候,線程池便會把該結果填入到之前給我們的那個Future中去(而不是創建一個新的Future),我們此時便可以從該Future中獲得任務執行的結果。
public class FutureDemo {
public static void main(String[] args) throws Exception {
//創建線程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 5, 5, TimeUnit.SECONDS, new LinkedBlockingQueue(10));
//獲取Future對象,使用Lambl打印一個隨機值。
Future<Integer> future = threadPoolExecutor.submit(()-> new Random().nextInt());
try {
//前面說過,get()方法返回值可能會拋出異常。
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
- 用FutureTask返回Future對象
把Callable實例當做參數,生成FutureTask的對象,然後把這個對象當做一個Runnable對象,用線程池或另起線程去執行這個Runnable對象,最後通過FutureTask獲取剛纔執行的結果。
public class FutureDemo {
public static void main(String[] args) {
FutureTask<Integer> future2 = new FutureTask<>(()-> new Random().nextInt());
new Thread(future2).start();
try {
System.out.println(future2.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
//不起作用。複用
new Thread(future2).start();
try {
System.out.println(future2.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
代碼中執行了2次
new Thread(future2).start();
得出的2條結果都是一樣的。
這就是達到了複用。
注意點
- 當for循環批量獲取Future的結果時,容易發生一部分線程很慢的情況(如果第一個線程計算很慢,後面的線程都計算結束了,那將會造成整個獲取結果很慢或阻塞),get()方法調用時應使用Timeout限制。
List<Future> futures = new ArrayList<>();
- Future的生命週期不能後退
生命週期只能前進,不能後退。就和線程池的生命週期一樣,一旦完全完成了任務,他就永遠停在了“已完成”的狀態,不能重頭再來。
上面的代碼複用就是這種是思想。
文章持續更新,可以微信搜索「 紳堂Style 」第一時間閱讀,回覆【資料】有我準備的面試題筆記。
GitHub https://github.com/dtt11111/Nodes 有總結面試完整考點、資料以及我的系列文章。歡迎Star。