Java 并發(fā)編程面試題Future 模式及實(shí)現(xiàn)方法
1.什么是 Future 模式?Java 中是如何實(shí)現(xiàn)的?
(1)Future 模式是一種并發(fā)編程模式,它允許異步執(zhí)行代碼并在未來(lái)獲取其結(jié)果。在 Future 模式中,調(diào)用線(xiàn)程可以提交一個(gè)任務(wù)給另一個(gè)線(xiàn)程或線(xiàn)程池,并立即返回一個(gè) Future 對(duì)象作為任務(wù)的代理。Future 對(duì)象表示了尚未完成的任務(wù),并允許調(diào)用線(xiàn)程在未來(lái)的某個(gè)時(shí)刻獲取任務(wù)的結(jié)果。Future 模式通常用于處理長(zhǎng)時(shí)間運(yùn)行的任務(wù),例如網(wǎng)絡(luò)請(qǐng)求或耗時(shí)的計(jì)算。通過(guò)使用 Future 模式,調(diào)用線(xiàn)程可以避免阻塞并繼續(xù)執(zhí)行其他任務(wù),同時(shí)仍然能夠獲得任務(wù)的結(jié)果。
(2)在 Java 中,F(xiàn)uture 模式是通過(guò) Future 接口來(lái)實(shí)現(xiàn)的。Java 還提供了 CompletableFuture 類(lèi),它是 Future 接口的實(shí)現(xiàn),并提供了更豐富的功能,例如異步回調(diào)和異常處理。Java 設(shè)計(jì)到的相關(guān)接口和類(lèi)如下圖所示:

2.Callable、Future 與 FutureTask 分別是什么?
通常來(lái)說(shuō),我們使用 Runnable 和 Thread 來(lái)創(chuàng)建一個(gè)新的線(xiàn)程。但是它們有一個(gè)弊端,就是 run() 是沒(méi)有返回值的。而有時(shí)候我們希望開(kāi)啟一個(gè)線(xiàn)程去執(zhí)行一個(gè)任務(wù),并且這個(gè)任務(wù)執(zhí)行完成后有一個(gè)返回值。JDK 提供了 Callable 接口與 Future 類(lèi)為我們解決這個(gè)問(wèn)題,這也是所謂的“異步”模型。
2.1.Callable 接口
(1)Callable 與 Runnable 類(lèi)似,同樣是只有?個(gè)抽象方法的函數(shù)式接看。不同的是, Callable 提供的方法是有返回值的,而且支持泛型。Callable 接口的特點(diǎn)如下:
- 為了實(shí)現(xiàn) Runnable,需要實(shí)現(xiàn)不返回任何內(nèi)容的 run() 方法,而對(duì)于Callable,需要實(shí)現(xiàn)在完成時(shí)返回結(jié)果的 call() 方法;
- call() 方法可以引發(fā)異常,而 run() 則不能;
- 為實(shí)現(xiàn) Callable 而必須重寫(xiě) call() 方法;
- 不能直接替換 runnable,因?yàn)?Thread 類(lèi)的構(gòu)造方法根本沒(méi)有 Callable;
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}class MyThread1 implements Runnable {
@Override
public void run() {
//無(wú)返回值
}
}
class MyThread2 implements Callable {
@Override
public Object call() throws Exception {
return 1;
}
}(2)那?般是怎么使用 Callable 的呢? Callable?般配合線(xiàn)程池工具 ExecutorService 來(lái)使用。這里只介紹 ExecutorService 可以使用 submit 方法來(lái)讓?個(gè) Callable 接口執(zhí)行。它會(huì)返回?個(gè) Future ,我們后續(xù)的程序可以通過(guò)這個(gè) Future 的 get 方法得到結(jié)果。這里可以看?個(gè)簡(jiǎn)單的使用案例:
class Task implements Callable<Integer> {
@Override
public Integer call() throws Exception {
// 模擬計(jì)算需要 3 秒
Thread.sleep(3000);
return 2;
}
public static void main(String args[]) throws ExecutionException, InterruptedException {
// 使?
ExecutorService executor = Executors.newCachedThreadPool();
Task task = new Task();
// ExecutorService.submit() 方法返回的其實(shí)就是 Future 的實(shí)現(xiàn)類(lèi) FutureTask
Future<Integer> result = executor.submit(task);
//注意調(diào)? get ?法會(huì)阻塞當(dāng)前線(xiàn)程,直到得到結(jié)果,所以實(shí)際編碼中建議使?可以設(shè)置超時(shí)時(shí)間的重載 get ?法
System.out.println(result.get());
}
}輸出結(jié)果:
2
此外,Callable 可以配合 FutureTask 使用:
class Task implements Callable<Integer> {
@Override
public Integer call() throws Exception {
// 模擬計(jì)算需要 3 秒
Thread.sleep(3000);
return 2;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> task = new FutureTask<>(new Task());
new Thread(task).start();
Integer res = task.get();
//注意調(diào)? get ?法會(huì)阻塞當(dāng)前線(xiàn)程,直到得到結(jié)果,所以實(shí)際編碼中建議使?可以設(shè)置超時(shí)時(shí)間的重載 get ?法
System.out.println(res);
}
}2.2.Future 接口
(1)在 Java 中,F(xiàn)uture 類(lèi)是一個(gè)泛型接口,位于 java.util.concurrent 包下,其包含的方法如下:
package java.util.concurrent;
// V 表示任務(wù)返回值的類(lèi)型
public interface Future<V> {
//成功取消任務(wù)返回 true,否則返回 false
boolean cancel(boolean mayInterruptIfRunning);
//判斷任務(wù)是否被取消
boolean isCancelled();
//判斷任務(wù)是否已經(jīng)執(zhí)行完成
boolean isDone();
//獲取任務(wù)執(zhí)行結(jié)果
V get() throws InterruptedException, ExecutionException;
//指定時(shí)間內(nèi)沒(méi)有返回計(jì)算結(jié)果就拋出 TimeOutException 異常
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}簡(jiǎn)單理解 Future:現(xiàn)在有一個(gè)任務(wù),提交給了 Future 來(lái)處理。任務(wù)執(zhí)行期間我自己可以去做任何想做的事情。并且,在這期間我還可以取消任務(wù)以及獲取任務(wù)的執(zhí)行狀態(tài)。一段時(shí)間之后,我就可以 Future 那里直接取出任務(wù)執(zhí)行結(jié)果。
(2)cancel 方法是試圖取消?個(gè)線(xiàn)程的執(zhí)行。 注意是試圖取消,并不?定能取消成功。因?yàn)槿蝿?wù)可能已完成、已取消、或者?些其它因素不能取消,存在取消失敗的可能。boolean 類(lèi)型的返回值是“是否取消成功”的意思。參數(shù) paramBoolean 表示是否采用中斷的方式取消線(xiàn)程執(zhí)行。 所以有時(shí)候?yàn)榱俗屓蝿?wù)有能夠取消的功能,就使用 Callable 來(lái)代替 Runnable 。 如果為了可取消性而使用 Future 但又不提供可用的結(jié)果,則可以聲明 Future<?> 形式類(lèi)型、并返回 null 作為底層任務(wù)的結(jié)果。
2.3.FutureTask 類(lèi)
(1)上面介紹了 Future 接口。這個(gè)接口有?個(gè)實(shí)現(xiàn)類(lèi)叫 FutureTask 。 FutureTask 是實(shí)現(xiàn)的 RunnableFuture 接口的,而 RunnableFuture 接口同時(shí)繼承了 Runnable 接口和 Future 接口:
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}(2)那 FutureTask 類(lèi)有什么用?前面說(shuō)到了 Future 只是?個(gè)接口,而它里面的 cancel、get、isDone 等方法要自己實(shí)現(xiàn)起來(lái)都是非常復(fù)雜的。所以 JDK 提供了?個(gè) FutureTask 類(lèi)來(lái)供我們使用。FutureTask 有兩個(gè)構(gòu)造函數(shù),可傳入 Callable 或者 Runnable 對(duì)象。實(shí)際上,傳入 Runnable 對(duì)象也會(huì)在方法內(nèi)部轉(zhuǎn)換為 Callable 對(duì)象:
public class FutureTask<V> implements RunnableFuture<V> {
//...
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW;
}
public FutureTask(Runnable runnable, V result) {
// 通過(guò)適配器 RunnableAdapter 來(lái)將 Runnable 對(duì)象 runnable 轉(zhuǎn)換成 Callable 對(duì)象
this.callable = Executors.callable(runnable, result);
this.state = NEW;
}
}FutureTask 相當(dāng)于對(duì) Callable 進(jìn)行了封裝,管理著任務(wù)執(zhí)行的情況,存儲(chǔ)了 Callable 的 call 方法的任務(wù)執(zhí)行結(jié)果。
(3)示例代碼如下:
class Task implements Callable<Integer> {
@Override
public Integer call() throws Exception {
//模擬計(jì)算需要?秒
Thread.sleep(1000);
return 2;
}
public static void main(String args[]) throws ExecutionException, InterruptedException {
ExecutorService executor = Executors.newCachedThreadPool();
FutureTask<Integer> futureTask = new FutureTask<>(new Task());
executor.submit(futureTask);
System.out.println(futureTask.get());
}
}使用上與第?個(gè) Demo 有?點(diǎn)小的區(qū)別:
- 此處調(diào)用 submit 方法是沒(méi)有返回值的,因?yàn)檫@里實(shí)際上是調(diào)用的
submit(Runnable task)方法,而上面的 Demo,調(diào)用的是submit(Callable<T> task)方法。 - 這里是使用 FutureTask 的 get 方法來(lái)獲取返回值,而上面的 Demo 是通過(guò) submit 方法返回的 Future 去取值。 在很多高并發(fā)的環(huán)境下,有可能 Callable 和 FutureTask 會(huì)創(chuàng)建多次。FutureTask 能夠在高并發(fā)環(huán)境下確保任務(wù)只執(zhí)行?次。
(4)核心原理
- 在主線(xiàn)程中需要執(zhí)行比較耗時(shí)的操作時(shí),但又不想阻塞主線(xiàn)程時(shí),可以把這些作業(yè)交給 Future 對(duì)象在后臺(tái)完成;
- 當(dāng)主線(xiàn)程將來(lái)需要時(shí),就可以通過(guò) Future 對(duì)象獲得后臺(tái)作業(yè)的計(jì)算結(jié)果或者執(zhí)行狀態(tài);
- 一般 FutureTask 多用于耗時(shí)的計(jì)算,主線(xiàn)程可以在完成自己的任務(wù)后,再去獲取結(jié)果;
- 僅在計(jì)算完成時(shí)才能檢索結(jié)果;如果計(jì)算尚未完成,則阻塞 get() 方法,一旦計(jì)算完成,就不能再重新開(kāi)始或取消計(jì)算;
- get() 方法而獲取結(jié)果只有在計(jì)算完成時(shí)獲取,否則會(huì)一直阻塞直到任務(wù)轉(zhuǎn)入完成狀態(tài),然后會(huì)返回結(jié)果或者拋出異常;
- get() 只計(jì)算一次,因此 get() 方法放到最后。
//比較 Runnable 和 Callable 這兩個(gè)接口
class MyThread1 implements Runnable {
@Override
public void run() {
//無(wú)返回值
}
}
class MyThread2 implements Callable {
@Override
public Object call() throws Exception {
return 1;
}
}
public class CallableDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//使用 Runnable 創(chuàng)建線(xiàn)程
new Thread(new MyThread1(), "AA").start();
/*
使用 Callable 創(chuàng)建線(xiàn)程
不能像上面那樣直接創(chuàng)建 new Thread(new MyThread2(), "BB").start();
*/
//FutureTask
FutureTask<Integer> futureTask1 = new FutureTask<>(new MyThread2());
//使用 lambda 表達(dá)式進(jìn)行簡(jiǎn)化
FutureTask<Integer> futureTask2 = new FutureTask<>(()->{
System.out.println(Thread.currentThread().getName() + " enters the callable .");
return 1;
});
//創(chuàng)建一個(gè)線(xiàn)程
new Thread(futureTask2, "Luck").start();
while (!futureTask2.isDone()) {
System.out.println("wait...");
}
//調(diào)用 FutureTask 的get()
System.out.println(futureTask2.get());
//只進(jìn)行一次計(jì)算
System.out.println(futureTask2.get());
System.out.println(Thread.currentThread().getName() + " is over !");
}
}(5)FutureTask 的幾種狀態(tài)
/** * state 可能的狀態(tài)轉(zhuǎn)變路徑如下: * NEW -> COMPLETING -> NORMAL * NEW -> COMPLETING -> EXCEPTIONAL * NEW -> CANCELLED * NEW -> INTERRUPTING -> INTERRUPTED */ private volatile int state; private static final int NEW = 0; private static final int COMPLETING = 1; private static final int NORMAL = 2; private static final int EXCEPTIONAL = 3; private static final int CANCELLED = 4; private static final int INTERRUPTING = 5; private static final int INTERRUPTED = 6;
state 表示任務(wù)的運(yùn)行狀態(tài),初始狀態(tài)為 NEW。運(yùn)行狀態(tài)只會(huì)在 set、setException、cancel 方法中終止。COMPLETING、INTERRUPTING 是任務(wù)完成后的瞬時(shí)狀態(tài)。
3.CompletableFuture 類(lèi)有什么用?
(1)Future 在實(shí)際使用過(guò)程中存在一些局限性,例如不支持異步任務(wù)的編排組合、獲取計(jì)算結(jié)果的 get() 方法為阻塞調(diào)用等。Java 8 才被引入 CompletableFuture 類(lèi)可以解決 Future 的這些缺陷。CompletableFuture 除了提供了更為好用和強(qiáng)大的 Future 特性之外,還提供了函數(shù)式編程、異步任務(wù)編排組合(可以將多個(gè)異步任務(wù)串聯(lián)起來(lái),組成一個(gè)完整的鏈?zhǔn)秸{(diào)用)等能力。
public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
//...
}(2)可以看到,CompletableFuture 同時(shí)實(shí)現(xiàn)了 Future 接口 和 CompletionStage 接口。其中,CompletionStage 接口描述了一個(gè)異步計(jì)算的階段。很多計(jì)算可以分成多個(gè)階段或步驟,此時(shí)可以通過(guò)它將所有步驟組合起來(lái),形成異步計(jì)算的流水線(xiàn)。CompletionStage 接口中的方法比較多,CompletableFuture 的函數(shù)式能力就是這個(gè)接口賦予的。從這個(gè)接口的方法參數(shù)可以發(fā)現(xiàn)其大量使用了 Java 8 引入的函數(shù)式編程。

到此這篇關(guān)于Java 并發(fā)編程面試題 Future 模式的文章就介紹到這了,更多相關(guān)Java Future 模式內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringCloud服務(wù)注冊(cè)中心數(shù)據(jù)一致性問(wèn)題
這篇文章主要介紹了SpringCloud服務(wù)注冊(cè)中心數(shù)據(jù)一致性問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-06-06
Java時(shí)間戳類(lèi)Instant的使用詳解
這篇文章主要為大家詳細(xì)介紹了Java中時(shí)間戳類(lèi)Instant的使用方法,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Java有一定幫助,需要的可以參考一下2022-09-09
maven父子工程中的依賴(lài)引用的實(shí)現(xiàn)
本文主要介紹了Maven父子工程中的依賴(lài)引用的實(shí)現(xiàn),包括<dependencyManagement>和<dependencies>兩個(gè)標(biāo)簽的使用,以及父子pom文件的配置,感興趣的可以了解一下2024-10-10
java使用數(shù)組和鏈表實(shí)現(xiàn)隊(duì)列示例
隊(duì)列是一種特殊的線(xiàn)性表,它只允許在表的前端(front)進(jìn)行刪除操作,只允許在表的后端(rear)進(jìn)行插入操作,下面介紹一下java使用數(shù)組和鏈表實(shí)現(xiàn)隊(duì)列的示例2014-01-01
利用Java寫(xiě)一個(gè)學(xué)生管理系統(tǒng)
今天這篇文章就給給大家分享利用Java寫(xiě)一個(gè)學(xué)生管理系統(tǒng)吧,先寫(xiě)一個(gè)簡(jiǎn)單的用List來(lái)實(shí)現(xiàn)學(xué)生管理系統(tǒng):2021-09-09
解決在啟動(dòng)eclipse的tomcat進(jìn)行訪(fǎng)問(wèn)時(shí)出現(xiàn)404問(wèn)題的方法
這篇文章主要介紹了解決在啟動(dòng)eclipse的tomcat進(jìn)行訪(fǎng)問(wèn)時(shí)出現(xiàn)404問(wèn)題的方法,感興趣的小伙伴們可以參考一下2016-04-04
Java concurrency之公平鎖(一)_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要為大家詳細(xì)介紹了Java concurrency之公平鎖的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06

