拿下美團實習~
最近正是找實習的大好時機,互聯(lián)網(wǎng)各家也分別官宣了他們的招聘計劃。
京 * 宣布要招聘 10000 人,騰 * 宣布要招聘 7000 人,字 * 宣布要招 4000 人,美 * 宣布了他們要招聘 5000 人,并且公布了 70% 的高轉正率:
(圖片來自:美團公眾號,侵權聯(lián)系可刪)
這不,這兩天有同學面試美團,被拷打了 1 個多小時,其中有一道問題印象深刻:如何實現(xiàn)多線程任務編排?
接下來咱們就具體聊聊這個問題。
定義
線程編排定義:多線程任務編排指的是對多個線程任務按照一定的邏輯順序或條件進行組織和安排,以實現(xiàn)協(xié)同工作、順序執(zhí)行或并行執(zhí)行的一種機制。
如下圖所示,其中任務二需要等任務一執(zhí)行完成之后再執(zhí)行,而任務四要等任務二和任務三執(zhí)行完成之后再執(zhí)行,這個時候就需要任務編排機制來保證任務的執(zhí)行順序:
實現(xiàn)方式
線程的任務編排的實現(xiàn)方式主要有以下兩種:
- FutureTask:誕生于 JDK 1.5,它實現(xiàn)了 Future 接口和 Runnable 接口,設計初衷是為了支持可取消的異步計算。它既可以承載 Runnable 任務(通過包裝成 RunnableAdapter),也可以承載 Callable 任務,從而能夠返回計算結果,使用它可以實現(xiàn)簡單的異步任務執(zhí)行和結果的等待。
- CompletableFuture:誕生于 JDK 8,它不僅實現(xiàn)了 Future 接口,還實現(xiàn)了 CompletionStage 接口。CompletionStage 是對 Future 的擴展,提供了豐富的鏈式異步編程模型,支持函數(shù)式編程風格,可以更加靈活地處理異步操作的組合和依賴回調等。
FutureTask 使用案例
FutureTask 使用示例如下:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FutureTaskDemo {
public static void main(String[] args) {
// 創(chuàng)建一個Callable任務
Callable<Integer> task = () -> {
Thread.sleep(2000); // 模擬任務耗時操作
return 10; // 返回任務結果
};
// 創(chuàng)建FutureTask,并將Callable任務包裝起來
FutureTask<Integer> futureTask = new FutureTask<>(task);
// 創(chuàng)建線程池
ExecutorService executor = Executors.newCachedThreadPool();
// 提交FutureTask給線程池執(zhí)行
executor.submit(futureTask);
try {
// 獲取任務結果,get()方法會阻塞直到任務完成并返回結果
int result = futureTask.get();
System.out.println("任務結果:" + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
在上述示例中,通過創(chuàng)建一個 Callable 任務來模擬耗時操作,并使用 FutureTask 包裝該任務。然后將 FutureTask 提交給線程池執(zhí)行,最后通過 get() 方法獲取任務的執(zhí)行結果,之后才會執(zhí)行后續(xù)流程。我們可以通過 get() 方法阻塞等待程序執(zhí)行結果,從而完成線程任務的簡單編排。
CompletableFuture 使用案例
從上面 FutureTask 實現(xiàn)代碼可以看出,它不但寫法麻煩,而且需要使用 get() 方法阻塞等待線程的執(zhí)行結果,對于異步任務的執(zhí)行來說,不夠靈活且效率也會受影響,然而 CompletableFutrue 的出現(xiàn),則彌補了 FutureTask 的這些缺陷。
CompletableFutrue 提供的方法有很多,但最常用和最實用的核心方法只有以下幾個:
例如,我們現(xiàn)在實現(xiàn)一個這樣的場景:
任務描述:任務一執(zhí)行完之后執(zhí)行任務二,任務三和任務一和任務二一起執(zhí)行,所有任務都有返回值,等任務二和任務三執(zhí)行完成之后,再執(zhí)行任務四,它的實現(xiàn)代碼如下:
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureExample {
public static void main(String[] args) {
// 任務一:返回 "Task 1 result"
CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> {
try {
// 模擬耗時操作
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
return "Task 1 result";
});
// 任務二:依賴任務一,返回 "Task 2 result" + 任務一的結果
CompletableFuture<String> task2 = task1.handle((result1, throwable) -> {
try {
// 模擬耗時操作
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
return "Task 2 result " + result1;
});
// 任務三:和任務一、任務二并行執(zhí)行,返回 "Task 3 result"
CompletableFuture<String> task3 = CompletableFuture.supplyAsync(() -> {
try {
// 模擬耗時操作
Thread.sleep(800); // 任務三可能比任務二先完成
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
return "Task 3 result";
});
// 任務四:依賴任務二和任務三,等待它們都完成后執(zhí)行,返回 "Task 4 result" + 任務二和任務三的結果
CompletableFuture<String> task4 = CompletableFuture.allOf(task2, task3).handle((res, throwable) -> {
try {
// 這里不需要顯式等待,因為 allOf 已經(jīng)保證了它們完成
return "Task 4 result with " + task2.get() + " and " + task3.get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
});
// 獲取任務四的結果并打印
String finalResult = task4.join();
System.out.println(finalResult);
}
}
課后思考
CompletableFuture 底層實現(xiàn)原理是啥?CompletableFuture 需要配合線程池一起使用嗎?為什么?
#八股文##java#Java常見面試題、場景題、企業(yè)真題精講。