Java開(kāi)發(fā)異步編程中常用的接口和類(lèi)示例詳解
Callable 與 Runnable接口
- Callable接口類(lèi)似于Runnable接口,都是用于實(shí)現(xiàn)一個(gè)線程任務(wù)。
- Callable實(shí)現(xiàn)的線程可以返回執(zhí)行結(jié)果,但是Runnable不可以返回執(zhí)行結(jié)果。
- Callable的run方法會(huì)拋出編譯時(shí)異常,而Runnable沒(méi)有拋出異常。
- 可以使用工廠類(lèi)Executors把Runnable包裝成Callable。
Runnable使用方式一:使用Thread運(yùn)行
public class ThreadCreationDemo {
// 第一種方式需要一個(gè)實(shí)現(xiàn)了Runnable接口的類(lèi)
static class MyRunnable implements Runnable {
@Override
public void run() {
// 打印當(dāng)前執(zhí)行此代碼的線程的名稱(chēng)
System.out.println("方法一 (MyRunnable): 線程 " + Thread.currentThread().getName() + " 正在運(yùn)行。");
}
}
public static void main(String[] args) {
// 第一種方式: 使用一個(gè)獨(dú)立的類(lèi)去實(shí)現(xiàn)Runnable接口
// 這種方法結(jié)構(gòu)清晰,適用于邏輯比較復(fù)雜的場(chǎng)景。
Thread t1 = new Thread(new MyRunnable());
t1.start();
// 第二種方式: 使用Lambda表達(dá)式實(shí)現(xiàn)一個(gè)Runnable
// 這是Java 8+中最推薦的簡(jiǎn)潔寫(xiě)法。
Thread t2 = new Thread(() -> {
System.out.println("方法二 (Lambda): 線程 " + Thread.currentThread().getName() + " 正在運(yùn)行。");
});
t2.start();
// 第三種方式: 使用匿名內(nèi)部類(lèi)實(shí)現(xiàn)一個(gè)Runnable
// 這是在Java 8出現(xiàn)之前常用的方法。
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("方法三 (匿名內(nèi)部類(lèi)): 線程 " + Thread.currentThread().getName() + " 正在運(yùn)行。");
}
});
t3.start();
}
}
Runnable使用方式二:使用線程池執(zhí)行
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class ExecutorServiceDemo {
// 定義一個(gè)實(shí)現(xiàn)了Runnable接口的類(lèi),用于作為要執(zhí)行的任務(wù)
static class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("任務(wù)由線程: " + Thread.currentThread().getName() + " 執(zhí)行。");
try {
// 模擬任務(wù)執(zhí)行耗時(shí)
Thread.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public static void main(String[] args) {
// 第二種方式: 使用“線程池”去運(yùn)行Runnable
// 使用Executors工廠類(lèi)創(chuàng)建一個(gè)單線程的線程池
// 這個(gè)線程池保證任務(wù)是按提交順序依次執(zhí)行的
ExecutorService executorService = Executors.newSingleThreadExecutor();
System.out.println("使用 executorService.execute() 提交任務(wù)。");
// execute(Runnable): 提交一個(gè)任務(wù),沒(méi)有返回值。
executorService.execute(new MyRunnable());
System.out.println("使用 executorService.submit() 提交任務(wù)。");
// submit(Runnable): 同樣提交一個(gè)任務(wù),但會(huì)返回一個(gè)Future對(duì)象。
// 這個(gè)Future對(duì)象可以用來(lái)判斷任務(wù)是否執(zhí)行完畢.
Future<?> future = executorService.submit(new MyRunnable());
// --- 把Runnable通過(guò)工具類(lèi)轉(zhuǎn)成Callable ---
// Callable與Runnable類(lèi)似,但它可以有返回值并能拋出異常。
// Executors.callable()方法可以將一個(gè)Runnable轉(zhuǎn)換為Callable,
// 其執(zhí)行結(jié)果為null。
Callable<Object> callable = Executors.callable(new MyRunnable());
System.out.println("提交轉(zhuǎn)換后的Callable任務(wù)。");
Future<Object> callableFuture = executorService.submit(callable);
// --- 關(guān)閉線程池 ---
executorService.shutdown();
}
}
Callable使用方式一:包裝到FutureTask里面,然后通過(guò)Thread運(yùn)行
因?yàn)镕utureTask實(shí)現(xiàn)了Runnable接口,所以可以作為參數(shù)傳入Thread中
一般寫(xiě)法
代碼示例:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class FutureTaskDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 1. 創(chuàng)建一個(gè)有返回值的任務(wù) (Callable)
Callable<String> myTask = () -> {
System.out.println("子線程: 正在執(zhí)行任務(wù)...");
Thread.sleep(2000); // 模擬耗時(shí)2秒
return "任務(wù)執(zhí)行完畢,這是結(jié)果";
};
// 2. 將任務(wù)包裝成 FutureTask (如圖所示)
FutureTask<String> futureTask = new FutureTask<>(myTask);
// 3. 把 FutureTask 交給一個(gè)新線程去執(zhí)行 (如圖所示)
// 因?yàn)?FutureTask 實(shí)現(xiàn)了 Runnable 接口,所以 Thread 可以直接運(yùn)行它
Thread thread = new Thread(futureTask);
thread.start();
System.out.println("主線程: 已啟動(dòng)子線程,我先忙別的...");
// 4. 在主線程中獲取任務(wù)結(jié)果
// get() 方法會(huì)阻塞當(dāng)前線程,直到子線程的任務(wù)完成
System.out.println("主線程: 現(xiàn)在需要結(jié)果了,開(kāi)始等待...");
String result = futureTask.get(); // 等待并獲取結(jié)果
System.out.println("主線程: 終于拿到了結(jié)果 - \"" + result + "\"");
}
}
運(yùn)行結(jié)果:
主線程: 已啟動(dòng)子線程,我先忙別的...
子線程: 正在執(zhí)行任務(wù)...
主線程: 現(xiàn)在需要結(jié)果了,開(kāi)始等待...
(程序在這里會(huì)暫停2秒)
主線程: 終于拿到了結(jié)果 - "任務(wù)執(zhí)行完畢,這是結(jié)果"
也可以用匿名內(nèi)部類(lèi)和Lambda表達(dá)式來(lái)定義
Callable任務(wù)
匿名內(nèi)部類(lèi)寫(xiě)法
匿名內(nèi)部類(lèi)是在Java 8之前的標(biāo)準(zhǔn)寫(xiě)法,語(yǔ)法上會(huì)顯得比較冗長(zhǎng)。
// 使用匿名內(nèi)部類(lèi)來(lái)定義 Callable 任務(wù)
FutureTask<String> futureTask_anonymous = new FutureTask<>(new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("通過(guò)匿名內(nèi)部類(lèi)執(zhí)行...");
Thread.sleep(1000);
return "匿名內(nèi)部類(lèi)的結(jié)果";
}
});
// 然后同樣交給 Thread 或線程池執(zhí)行
// Thread thread = new Thread(futureTask_anonymous);
// thread.start();
Lambda表達(dá)式寫(xiě)法
Lambda表達(dá)式是Java 8及以后推薦的函數(shù)式編程寫(xiě)法,非常簡(jiǎn)潔。
由于Callable接口是一個(gè)函數(shù)式接口(只有一個(gè)抽象方法call()),所以可以被Lambda表達(dá)式替代。
// 使用Lambda表達(dá)式來(lái)定義 Callable 任務(wù)
FutureTask<String> futureTask_lambda = new FutureTask<>(() -> {
System.out.println("通過(guò)Lambda表達(dá)式執(zhí)行...");
Thread.sleep(1000);
return "Lambda的結(jié)果";
});
// 然后同樣交給 Thread 或線程池執(zhí)行
// Thread thread = new Thread(futureTask_lambda);
// thread.start();
Callable使用方式二:使用線程池執(zhí)行
execute方法只能接收Runnable對(duì)象,所以Callable對(duì)象不能使用線程池的execute方法執(zhí)行,只能使用submit方法執(zhí)行。
執(zhí)行會(huì)返回Future類(lèi)的對(duì)象,對(duì)這個(gè)對(duì)象調(diào)用get()方法即可以拿到返回值。
示例代碼:
import java.util.concurrent.*;
public class ExecutorServiceDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 1. 創(chuàng)建一個(gè)線程池 (圖中是單線程的,更常用的是固定大小的線程池)
ExecutorService executorService = Executors.newFixedThreadPool(2); // 例如,一個(gè)有兩個(gè)線程的池
System.out.println("主線程: 準(zhǔn)備將任務(wù)提交給線程池。");
// 2. 將 Callable 任務(wù)提交給線程池 (直接使用Lambda表達(dá)式定義任務(wù))
// submit() 方法會(huì)立即返回一個(gè) Future 對(duì)象
Future<String> future = executorService.submit(() -> {
System.out.println("子線程 (" + Thread.currentThread().getName() + "): 開(kāi)始執(zhí)行耗時(shí)任務(wù)...");
Thread.sleep(2000); // 模擬耗時(shí)2秒
return "任務(wù)完成,這是來(lái)自線程池的結(jié)果";
});
System.out.println("主線程: 任務(wù)已提交,我可以繼續(xù)做其他事情...");
// 在這里,主線程可以執(zhí)行其他不受阻塞的代碼
// 3. 在需要結(jié)果時(shí),調(diào)用 future.get() 阻塞等待
System.out.println("主線程: 現(xiàn)在需要結(jié)果了,開(kāi)始等待...");
String result = future.get(); // 阻塞,直到子線程任務(wù)完成并返回結(jié)果
System.out.println("主線程: 成功獲取到結(jié)果 -> \"" + result + "\"");
// 4. 關(guān)閉線程池 (這是必須的步驟,否則JVM不會(huì)退出)
executorService.shutdown();
}
}
Future接口
- 一個(gè)Future表示一個(gè)異步任務(wù)的結(jié)果;
- 它提供了一些方法用來(lái)檢查異步是不是已經(jīng)完成,沒(méi)有完成就等待,待其完成后取回異步執(zhí)行的結(jié)果;
- 異步執(zhí)行的結(jié)果,只能通過(guò)get()方法獲取,get()方法是阻塞的,如果異步執(zhí)行沒(méi)有執(zhí)行結(jié)束 ,則阻塞直至拿到結(jié)果;
- 它提供了一個(gè)cancel()方法用于取消異步執(zhí)行,執(zhí)行完畢后不可取消;
- 如果不想要返回的結(jié)果,也可以把底層的任務(wù)聲明為返回null。
Future接口有5個(gè)方法:

boolean isDone(): 判斷任務(wù)是否已經(jīng)完成。V get(): 阻塞等待,直到任務(wù)完成并返回結(jié)果。V get(long timeout, TimeUnit unit): 在指定時(shí)間內(nèi)阻塞等待,超時(shí)則拋出TimeoutException。boolean isCancelled(): 判斷任務(wù)是否在完成前被取消。boolean cancel(boolean mayInterruptIfRunning): 嘗試取消任務(wù)的執(zhí)行。
import java.util.concurrent.*;
public class FutureMethodsDemo {
public static void main(String[] args) {
// 創(chuàng)建一個(gè)固定大小為2的線程池
ExecutorService executor = Executors.newFixedThreadPool(2);
System.out.println("--- 演示 get(), isDone() ---");
// 提交一個(gè) Callable 任務(wù),該任務(wù)會(huì)模擬2秒的計(jì)算,然后返回一個(gè)字符串結(jié)果
Future<String> future1 = executor.submit(() -> {
System.out.println("任務(wù)1: 開(kāi)始執(zhí)行,預(yù)計(jì)耗時(shí)2秒...");
Thread.sleep(2000);
return "任務(wù)1執(zhí)行完畢!";
});
// 1. isDone()
// 在任務(wù)剛提交時(shí),檢查它是否完成
System.out.println("提交任務(wù)1后,立即檢查 isDone(): " + future1.isDone());
try {
// 2. get()
// 這是一個(gè)阻塞方法,主線程會(huì)在這里等待,直到任務(wù)1執(zhí)行完成并返回結(jié)果
System.out.println("調(diào)用 future1.get(),主線程等待中...");
String result1 = future1.get();
System.out.println("future1.get() 獲得結(jié)果: \"" + result1 + "\"");
// 任務(wù)完成后,再次檢查isDone()
System.out.println("任務(wù)1完成后,再次檢查 isDone(): " + future1.isDone());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
System.out.println("\n--- 演示 get(timeout, unit) ---");
// 提交一個(gè)需要3秒才能完成的任務(wù)
Future<String> future2 = executor.submit(() -> {
System.out.println("任務(wù)2: 開(kāi)始執(zhí)行,預(yù)計(jì)耗時(shí)3秒...");
Thread.sleep(3000);
return "任務(wù)2執(zhí)行完畢!";
});
try {
// 3. get(long timeout, TimeUnit unit)
// 我們只等待1秒,由于任務(wù)需要3秒,所以這必定會(huì)超時(shí)
System.out.println("調(diào)用 future2.get(1, TimeUnit.SECONDS),主線程最多等待1秒...");
String result2 = future2.get(1, TimeUnit.SECONDS);
System.out.println("在1秒內(nèi)獲取到結(jié)果: " + result2);
} catch (TimeoutException e) {
// 這里會(huì)捕獲到超時(shí)異常
System.out.println("等待超時(shí)! " + e.getClass().getName());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
System.out.println("\n--- 演示 cancel(), isCancelled() ---");
// 提交一個(gè)長(zhǎng)時(shí)間運(yùn)行的任務(wù)
Future<String> future3 = executor.submit(() -> {
System.out.println("任務(wù)3: 開(kāi)始執(zhí)行,這是一個(gè)可能被中斷的長(zhǎng)任務(wù)...");
try {
Thread.sleep(5000); // 模擬一個(gè)非常耗時(shí)的操作
} catch (InterruptedException e) {
// 如果任務(wù)被中斷,會(huì)進(jìn)入這里
System.out.println("任務(wù)3: 執(zhí)行被中斷!");
return "已被中斷";
}
return "任務(wù)3正常完成";
});
// 為了確保 cancel() 在任務(wù)開(kāi)始后執(zhí)行,我們稍微等待一下
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 4. cancel(boolean mayInterruptIfRunning)
// 嘗試取消任務(wù)的執(zhí)行。參數(shù)true表示如果任務(wù)已經(jīng)在運(yùn)行,就中斷執(zhí)行它的線程。
System.out.println("嘗試取消任務(wù)3...");
boolean cancelResult = future3.cancel(true);
System.out.println("cancel(true) 方法返回: " + cancelResult);
// 5. isCancelled()
// 檢查任務(wù)是否已經(jīng)被成功取消
System.out.println("調(diào)用cancel后,檢查 isCancelled(): " + future3.isCancelled());
// 再次檢查isDone()。一個(gè)被取消的任務(wù)也被認(rèn)為是“完成”的。
System.out.println("調(diào)用cancel后,檢查 isDone(): " + future3.isDone());
try {
// 試圖獲取一個(gè)已取消任務(wù)的結(jié)果,會(huì)拋出 CancellationException
System.out.println("嘗試 get() 已取消的任務(wù)3...");
String result3 = future3.get();
System.out.println("獲取到已取消任務(wù)的結(jié)果: " + result3);
} catch (CancellationException e) {
System.out.println("獲取結(jié)果失敗,因?yàn)槿蝿?wù)已被取消! " + e.getClass().getName());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
// 關(guān)閉線程池,這是一個(gè)好習(xí)慣
executor.shutdownNow();
}
}
FutureTask類(lèi)

基本特點(diǎn)
- FutureTask類(lèi)實(shí)現(xiàn)了Runnable接口、Future接口(RunnableFuture接口)。
- FutureTask類(lèi)除了實(shí)現(xiàn)接口的5+1個(gè)方法,還有兩個(gè)構(gòu)造方法。
- FutureTask代表了一個(gè)可以取消的異步執(zhí)行。
- FutureTask可以用來(lái)包裝Runnable或者Callable。
- FutureTask可以提交到Executor中去執(zhí)行。
import java.util.concurrent.*;
public class FutureTaskFeaturesDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// =================================================================
// 特點(diǎn) 5: FutureTask 可以用來(lái)包裝 Runnable 或者 Callable
// =================================================================
// 創(chuàng)建一個(gè) Callable 任務(wù),它會(huì)模擬計(jì)算并返回一個(gè)結(jié)果
Callable<String> callableTask = () -> {
System.out.println("子線程 (來(lái)自Callable): 正在進(jìn)行復(fù)雜的計(jì)算...");
Thread.sleep(2000); // 模擬耗時(shí)操作
return "計(jì)算完成,結(jié)果是ABC";
};
// 創(chuàng)建一個(gè) Runnable 任務(wù),它沒(méi)有返回值
Runnable runnableTask = () -> {
System.out.println("子線程 (來(lái)自Runnable): 正在執(zhí)行一個(gè)任務(wù)...");
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
};
// 使用 Callable 構(gòu)造 FutureTask
FutureTask<String> futureTaskFromCallable = new FutureTask<>(callableTask);
// 使用 Runnable 構(gòu)造 FutureTask,需要提供一個(gè)默認(rèn)的返回結(jié)果
String defaultResult = "Runnable執(zhí)行完畢";
FutureTask<String> futureTaskFromRunnable = new FutureTask<>(runnableTask, defaultResult);
// =================================================================
// 特點(diǎn) 2 & 6: FutureTask 是一個(gè)實(shí)現(xiàn)了 Runnable 和 Future 的類(lèi),可以提交到 Executor 執(zhí)行
// =================================================================
// 因?yàn)?FutureTask 實(shí)現(xiàn)了 Runnable,所以它可以被線程池執(zhí)行
ExecutorService executor = Executors.newFixedThreadPool(2);
System.out.println("主線程: 將 futureTaskFromCallable 提交到線程池執(zhí)行。");
executor.submit(futureTaskFromCallable); // 特點(diǎn)6的演示
// =================================================================
// 特點(diǎn) 1 & 3: 代表可取消的異步計(jì)算,結(jié)果只能在執(zhí)行完成后才能獲取 (get() 會(huì)阻塞)
// =================================================================
System.out.println("主線程: 嘗試獲取 futureTaskFromCallable 的結(jié)果...");
// 在任務(wù)完成前,isDone() 返回 false (特點(diǎn)2的演示)
System.out.println("主線程: 任務(wù)完成了嗎? " + futureTaskFromCallable.isDone());
// 調(diào)用 get() 方法,主線程會(huì)在這里阻塞,直到子線程中的任務(wù)執(zhí)行完畢 (特點(diǎn)3的演示)
String result = futureTaskFromCallable.get(); // 阻塞點(diǎn)
System.out.println("主線程: 終于等到了結(jié)果 - " + result); // 特點(diǎn)1的演示
// 任務(wù)完成后,isDone() 返回 true
System.out.println("主線程: 任務(wù)現(xiàn)在完成了嗎? " + futureTaskFromCallable.isDone());
System.out.println("------------------------------------------");
// =================================================================
// 特點(diǎn) 4: 一旦執(zhí)行完成,則不能重新開(kāi)始執(zhí)行,也不能取消
// =================================================================
System.out.println("主線程: 演示任務(wù)完成后的狀態(tài)。");
// 嘗試取消一個(gè)已經(jīng)完成的任務(wù)
boolean cancelResult = futureTaskFromCallable.cancel(true);
System.out.println("主線程: 嘗試取消已完成的任務(wù),結(jié)果: " + cancelResult);
System.out.println("主線程: 已完成的任務(wù)被取消了嗎? " + futureTaskFromCallable.isCancelled());
// 再次提交已經(jīng)完成的任務(wù)到線程池
// 注意:這不會(huì)讓任務(wù)的 run() 方法被再次執(zhí)行。
// FutureTask 內(nèi)部有狀態(tài)控制,一旦任務(wù)完成(正常、異?;蛉∠臓顟B(tài)就不會(huì)再改變。
System.out.println("主線程: 再次提交同一個(gè)已完成的FutureTask...");
executor.submit(futureTaskFromCallable); // 提交是有效的,但任務(wù)的run方法不會(huì)再執(zhí)行
// 你可以再次 get(), 它會(huì)立刻返回之前已經(jīng)計(jì)算好的結(jié)果,而不會(huì)重新計(jì)算。
System.out.println("主線程: 再次get(),立即返回結(jié)果: " + futureTaskFromCallable.get());
System.out.println("------------------------------------------");
// 演示取消一個(gè)尚未完成的任務(wù) (再次體現(xiàn)特點(diǎn)1)
FutureTask<String> longRunningTask = new FutureTask<>(() -> {
System.out.println("子線程 (長(zhǎng)任務(wù)): 我要睡10秒,除非被中斷...");
Thread.sleep(10000);
return "我睡醒了";
});
executor.submit(longRunningTask);
Thread.sleep(100); // 確保任務(wù)已經(jīng)開(kāi)始運(yùn)行
System.out.println("主線程: 任務(wù)太慢了,決定取消它。");
longRunningTask.cancel(true); // 嘗試取消
System.out.println("主線程: 長(zhǎng)任務(wù)被取消了嗎? " + longRunningTask.isCancelled());
System.out.println("主線程: 長(zhǎng)任務(wù)算'完成'了嗎? " + longRunningTask.isDone());
try {
longRunningTask.get(); // 對(duì)已取消的任務(wù)調(diào)用get會(huì)拋出CancellationException
} catch (CancellationException e) {
System.out.println("主線程: 果然,獲取結(jié)果時(shí)拋出了 " + e.getClass().getSimpleName());
}
// 關(guān)閉線程池
executor.shutdown();
}
}
“不可重復(fù)執(zhí)行”特性(含源碼)
還有一點(diǎn)需要注意的就是,一旦執(zhí)行完成,就不能重新開(kāi)始執(zhí)行,也不能取消。
FutureTask之所以再次執(zhí)行沒(méi)有效果,是因?yàn)樗鼉?nèi)部設(shè)計(jì)了一個(gè)“一次性”的狀態(tài)機(jī)(State Machine)。一旦任務(wù)進(jìn)入“完成”狀態(tài)(無(wú)論是正常結(jié)束、異常終止還是被取消),它的狀態(tài)就無(wú)法再回到“未開(kāi)始”狀態(tài)。
示例如下:
錯(cuò)誤的做法:
Callable<String> taskLogic = () -> "Hello"; FutureTask<String> myTask = new FutureTask<>(taskLogic); executor.submit(myTask); // 第一次執(zhí)行,有效 executor.submit(myTask); // 第二次提交同一個(gè)對(duì)象,無(wú)效
正確的做法:
Callable<String> taskLogic = () -> "Hello"; // 業(yè)務(wù)邏輯可以復(fù)用 // 第一次執(zhí)行 FutureTask<String> task1 = new FutureTask<>(taskLogic); executor.submit(task1); System.out.println(task1.get()); // 第二次執(zhí)行,必須創(chuàng)建新對(duì)象 FutureTask<String> task2 = new FutureTask<>(taskLogic); executor.submit(task2); System.out.println(task2.get());
為什么會(huì)有這種現(xiàn)象?下面對(duì)原理進(jìn)行分析:
FutureTask被設(shè)計(jì)的首要目的不僅僅是“去執(zhí)行一個(gè)任務(wù)”,更是“持有(或代表)一個(gè)異步計(jì)算的結(jié)果”。
可以將其想象成一張一次性的彩票:
- 創(chuàng)建FutureTask: 買(mǎi)了一張彩票,彩票處于“未開(kāi)獎(jiǎng)”(
NEW)狀態(tài)。 - 執(zhí)行run()方法: 開(kāi)獎(jiǎng)過(guò)程開(kāi)始了。這是彩票唯一一次被“使用”的機(jī)會(huì)。
- 執(zhí)行完成: 開(kāi)獎(jiǎng)結(jié)束,彩票狀態(tài)變?yōu)?ldquo;已開(kāi)獎(jiǎng)”(
NORMAL/EXCEPTIONAL),結(jié)果(中獎(jiǎng)或未中獎(jiǎng))也已經(jīng)確定并記錄在彩票上。 - 調(diào)用get(): 隨時(shí)可以查看這張彩票的開(kāi)獎(jiǎng)結(jié)果。
- 再次執(zhí)行: 能用同一張已經(jīng)開(kāi)過(guò)獎(jiǎng)的彩票去參加下一輪的抽獎(jiǎng)嗎?顯然不能。它的使命已經(jīng)在第一次開(kāi)獎(jiǎng)時(shí)完成了。
FutureTask也是如此。它的run()方法被設(shè)計(jì)為最多只執(zhí)行一次。一旦執(zhí)行完畢,它就從一個(gè)“任務(wù)執(zhí)行器”轉(zhuǎn)變?yōu)橐粋€(gè)“結(jié)果容器”。任何后續(xù)對(duì)get()的調(diào)用都會(huì)立刻返回已經(jīng)緩存的結(jié)果,而任何再次執(zhí)行它的嘗試都會(huì)被直接忽略。
FutureTask內(nèi)部通過(guò)一個(gè)state字段來(lái)管理其生命周期。這個(gè)狀態(tài)流轉(zhuǎn)是單向的,不可逆轉(zhuǎn)。
其主要狀態(tài)有:
NEW(0): 任務(wù)已創(chuàng)建,但尚未開(kāi)始執(zhí)行。這是唯一可以開(kāi)始執(zhí)行任務(wù)的狀態(tài)。COMPLETING(1): 任務(wù)正在執(zhí)行中,即將完成但結(jié)果還未設(shè)置。這是一個(gè)臨時(shí)的中間狀態(tài)。NORMAL(2): 任務(wù)已正常執(zhí)行完畢,結(jié)果已經(jīng)成功設(shè)置。EXCEPTIONAL(3): 任務(wù)執(zhí)行過(guò)程中拋出了異常,異常信息已被保存。CANCELLED(4): 任務(wù)在執(zhí)行完成前被取消。INTERRUPTING(5): 任務(wù)正在被中斷的過(guò)程中。INTERRUPTED(6): 任務(wù)已經(jīng)被成功中斷。
關(guān)鍵點(diǎn):一旦狀態(tài)從 NEW 變?yōu)槿魏纹渌麪顟B(tài)(如NORMAL, EXCEPTIONAL, CANCELLED),就再也無(wú)法回到 NEW 狀態(tài)。
FutureTask的run()方法完美地體現(xiàn)了這種狀態(tài)檢查機(jī)制(以下為簡(jiǎn)化后的邏輯):
public void run() {
// 1. 關(guān)鍵檢查:如果當(dāng)前狀態(tài)不是 NEW,或者設(shè)置執(zhí)行線程失?。ㄕf(shuō)明其他線程搶先執(zhí)行了),
// 則直接返回,不執(zhí)行任何操作。
if (state != NEW || !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread()))
return;
try {
Callable<V> c = callable; // 獲取包裝的任務(wù)
if (c != null && state == NEW) {
V result;
boolean ran;
try {
// 2. 真正執(zhí)行任務(wù)
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
// 3. 如果有異常,設(shè)置異常結(jié)果,并將狀態(tài)變?yōu)?EXCEPTIONAL
setException(ex);
}
if (ran)
// 4. 如果正常完成,設(shè)置正常結(jié)果,并將狀態(tài)變?yōu)?NORMAL
set(result);
}
} finally {
// 清理工作
runner = null;
// ...
}
}
從源碼可以看出,run()方法的第一行就是一個(gè)防御性檢查。如果你拿著一個(gè)已經(jīng)執(zhí)行過(guò)(state不再是NEW)的FutureTask實(shí)例去提交給線程池,線程池調(diào)用它的run()方法時(shí),這個(gè)檢查會(huì)直接失敗,方法立即return,任務(wù)的邏輯自然就不會(huì)被再次執(zhí)行了。
set()方法是狀態(tài)變更的開(kāi)始。
// FutureTask.java
protected void set(V v) {
// 使用 CAS(Compare-And-Swap)原子地將狀態(tài)從 NEW 變?yōu)?COMPLETING
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
// outcome 字段用于存儲(chǔ)最終結(jié)果
this.outcome = v;
// 將最終狀態(tài)設(shè)置為 NORMAL (正常完成)
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
// *** 關(guān)鍵調(diào)用!進(jìn)入收尾階段 ***
finishCompletion();
}
}
- 這里有兩次狀態(tài)變更:首先原子地從
NEW變成一個(gè)臨時(shí)的COMPLETING狀態(tài),這是為了防止并發(fā)的cancel()操作。 - 然后設(shè)置
outcome字段保存結(jié)果。 - 最后,將狀態(tài)設(shè)置為最終的
NORMAL,并調(diào)用finishCompletion()。
finishCompletion() —— 喚醒所有等待者,這個(gè)方法負(fù)責(zé)喚醒所有因調(diào)用get()而被阻塞的線程。
// FutureTask.java
private void finishCompletion() {
// 'waiters' 是一個(gè)單向鏈表,存儲(chǔ)了所有正在等待結(jié)果的線程 (WaitNode)。
for (WaitNode q; (q = waiters) != null;) {
// 使用 CAS 將 'waiters' 鏈表頭置為 null,確保這個(gè)喚醒過(guò)程只被執(zhí)行一次。
if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
// 遍歷整個(gè)等待者鏈表
for (;;) {
Thread t = q.thread;
if (t != null) {
q.thread = null;
// *** 喚醒! ***
// 使用 LockSupport.unpark() 喚醒那個(gè)正在等待的線程。
// 那個(gè)線程會(huì)從 get() -> awaitDone() 的阻塞中醒來(lái)。
LockSupport.unpark(t);
}
WaitNode next = q.next;
if (next == null)
break;
q.next = null; // help GC
q = next;
}
break;
}
}
// 做一些完成后的清理工作
done();
callable = null; // a micro-optimization
}
- 核心邏輯: 該方法會(huì)原子地“摘下”整個(gè)等待者鏈表 (
waiters)。 - 然后,它會(huì)遍歷這個(gè)剛剛摘下的鏈表,并對(duì)其中的每一個(gè)節(jié)點(diǎn)所代表的線程,執(zhí)行
LockSupport.unpark(t)。 - 被
unpark的線程之前正阻塞在awaitDone()方法里,現(xiàn)在它被喚醒,就可以從awaitDone()返回,接著從get()方法拿到outcome中存儲(chǔ)的結(jié)果,然后繼續(xù)執(zhí)行。
整個(gè)流程串起來(lái)就是:
- A線程調(diào)用
futureTask.get()。get()發(fā)現(xiàn)任務(wù)未完成(state為NEW),于是將A線程包裝成一個(gè)WaitNode節(jié)點(diǎn),加入到waiters等待鏈表中,然后LockSupport.park()讓自身(A線程)掛起等待。 - B線程(線程池中的工作線程)開(kāi)始執(zhí)行
futureTask.run()。 run()方法檢查狀態(tài)為NEW,通過(guò)檢查,開(kāi)始執(zhí)行業(yè)務(wù)代碼。- 業(yè)務(wù)代碼執(zhí)行完畢,
run()調(diào)用set(result)。 set(result)將state從NEW最終改為NORMAL,把結(jié)果存入outcome,然后調(diào)用finishCompletion()。finishCompletion()遍歷waiters鏈表,找到代表A線程的節(jié)點(diǎn),并調(diào)用LockSupport.unpark(A線程)。- A線程被喚醒,從
get()方法中返回,并讀取outcome字段中已經(jīng)準(zhǔn)備好的結(jié)果。
因?yàn)?code>state的狀態(tài)被永久地改變?yōu)榱?code>NORMAL,并且finishCompletion保證了所有等待者都被喚醒,這個(gè)FutureTask的使命就此終結(jié)。任何后續(xù)對(duì)run()的調(diào)用都會(huì)在第一步的if (state != NEW)檢查中失敗,從而實(shí)現(xiàn)了“一次性”的語(yǔ)義。
從原始FutureTask對(duì)象中取值(含源碼)
將一個(gè)已經(jīng)創(chuàng)建好的FutureTask對(duì)象提交給ExecutorService時(shí),submit方法會(huì)返回一個(gè)新的、包裝了你原始任務(wù)的Future對(duì)象。而計(jì)算的真正結(jié)果,仍然存儲(chǔ)在原始的那個(gè)FutureTask對(duì)象里。調(diào)用這個(gè)新的包裝對(duì)象的get()方法,往往得到的是null,而不是想要的結(jié)果。
代碼示例:
import java.util.concurrent.*;
public class FutureTaskGetDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 1. 創(chuàng)建一個(gè) Callable,這是我們真正的業(yè)務(wù)邏輯
Callable<String> myCallable = () -> {
System.out.println("子線程: 正在執(zhí)行核心業(yè)務(wù)邏輯...");
Thread.sleep(1000);
return "這是真正的計(jì)算結(jié)果";
};
// 2. 創(chuàng)建我們自己的、原始的 FutureTask 對(duì)象
// 我們需要用這個(gè)對(duì)象來(lái)獲取最終結(jié)果
FutureTask<String> originalFutureTask = new FutureTask<>(myCallable);
ExecutorService executor = Executors.newSingleThreadExecutor();
// 3. 將我們?cè)嫉?FutureTask 提交給線程池
// submit(Runnable) 會(huì)返回一個(gè)新的 Future 對(duì)象,我們稱(chēng)之為 wrapperFuture
Future<?> wrapperFuture = executor.submit(originalFutureTask);
// 4. 分別從兩個(gè) Future 對(duì)象中獲取結(jié)果
// 錯(cuò)誤的做法:嘗試從 submit 方法返回的 Future 中獲取結(jié)果
// 這個(gè) get() 等待的是 wrapperFuture 的完成,其結(jié)果是 null
Object resultFromWrapper = wrapperFuture.get();
System.out.println("從 submit() 返回的 wrapperFuture.get() 拿到的結(jié)果是: " + resultFromWrapper);
System.out.println("wrapperFuture is done: " + wrapperFuture.isDone());
System.out.println("-------------------------------------------------");
// 正確的做法:從我們自己創(chuàng)建的原始 FutureTask 對(duì)象中獲取結(jié)果
// 這個(gè) get() 獲取的是存儲(chǔ)在 originalFutureTask 內(nèi)部的真實(shí)結(jié)果
String resultFromOriginal = originalFutureTask.get();
System.out.println("從原始的 originalFutureTask.get() 拿到的結(jié)果是: " + resultFromOriginal);
System.out.println("originalFutureTask is done: " + originalFutureTask.isDone());
executor.shutdown();
}
}
運(yùn)行結(jié)果:
子線程: 正在執(zhí)行核心業(yè)務(wù)邏輯...
從 submit() 返回的 wrapperFuture.get() 拿到的結(jié)果是: null
wrapperFuture is done: true
-------------------------------------------------
從原始的 originalFutureTask.get() 拿到的結(jié)果是: 這是真正的計(jì)算結(jié)果
originalFutureTask is done: true
從源碼的角度看這種原因:
第1步: 進(jìn)入ExecutorService.submit()
FutureTask本身實(shí)現(xiàn)了Runnable接口,所以這里實(shí)際調(diào)用的是submit(Runnable task)。我們看AbstractExecutorService中的實(shí)現(xiàn):
// AbstractExecutorService.java
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
// 關(guān)鍵!它調(diào)用了 newTaskFor 來(lái)創(chuàng)建一個(gè)新的 RunnableFuture 對(duì)象。
RunnableFuture<Void> ftask = newTaskFor(task, null);
// 然后執(zhí)行這個(gè)新創(chuàng)建的任務(wù)
execute(ftask);
// 最后返回這個(gè)新創(chuàng)建的任務(wù)
return ftask;
}
初始的FutureTask被作為對(duì)象傳入,newTaskFor(originalFutureTask, null)被調(diào)用了。這個(gè)方法默認(rèn)會(huì)返回一個(gè)新的FutureTask實(shí)例
所以,代碼等價(jià)于:RunnableFuture<Void> ftask = new FutureTask<Void>(originalFutureTask, null);
現(xiàn)在你就有了兩個(gè)FutureTask:
originalFutureTask: 你自己創(chuàng)建的,它包裝了myCallable,期望的結(jié)果是String類(lèi)型。wrapperFuture(即ftask):submit方法內(nèi)部創(chuàng)建的,它包裝了originalFutureTask,期望的結(jié)果是null。
第2步: 任務(wù)執(zhí)行
線程池實(shí)際執(zhí)行的是外層的wrapperFuture。
wrapperFuture.run()被調(diào)用。wrapperFuture的Callable是什么?是originalFutureTask自己(因?yàn)樗鼘?shí)現(xiàn)了Runnable)。所以,wrapperFuture的run方法內(nèi)部會(huì)調(diào)用originalFutureTask.run()。originalFutureTask.run()被調(diào)用。它會(huì)執(zhí)行最初的myCallable,計(jì)算出結(jié)果“真正的結(jié)果”。originalFutureTask執(zhí)行成功后,會(huì)調(diào)用set("真正的結(jié)果"),將結(jié)果保存在自己的outcome字段中。
第3步:finishCompletion()和關(guān)鍵的null操作
originalFutureTask在調(diào)用set()并最終進(jìn)入finishCompletion()方法后,會(huì)執(zhí)行一項(xiàng)重要的清理工作。
// FutureTask.java
private void finishCompletion() {
// ... (喚醒等待者線程的代碼) ...
done(); // 這是一個(gè)空方法,留給子類(lèi)擴(kuò)展
// *** 最關(guān)鍵的一行 ***
// 為了幫助垃圾回收,將內(nèi)部的 callable 引用置為 null。
// 因?yàn)槿蝿?wù)已經(jīng)執(zhí)行完了,理論上不再需要它了。
callable = null;
}
在originalFutureTask完成它的使命后,它扔掉了對(duì)myCallable的引用。它的outcome字段已經(jīng)安全地保存了結(jié)果“真正的結(jié)果”。
第4步: 結(jié)果返回
- 當(dāng)
originalFutureTask.run()執(zhí)行完畢后,wrapperFuture的run()方法也隨之結(jié)束。 wrapperFuture也成功完成了,它會(huì)調(diào)用set(null)(因?yàn)閯?chuàng)建它的時(shí)候newTaskFor(task, null)的第二個(gè)參數(shù)是null)。所以wrapperFuture的outcome字段保存的是null。
結(jié)論:
- 如果你調(diào)用
wrapperFuture.get(),你得到的是wrapperFuture的outcome,也就是null。 - 如果你調(diào)用
originalFutureTask.get(),你得到的是originalFutureTask的outcome,也就是 "真正的結(jié)果"。
新返回Future對(duì)象的作用
wrapperFuture的核心價(jià)值在于提供統(tǒng)一的控制和狀態(tài)管理接口,而不是為了傳遞結(jié)果。它是ExecutorService框架為了保持API一致性和健壯性而設(shè)計(jì)的關(guān)鍵一環(huán)。
任務(wù)取消是wrapperFuture最重要的用途之一。當(dāng)你把任務(wù)提交給ExecutorService后,你手中唯一能直接操作的句柄就是submit方法返回的wrapperFuture。你需要通過(guò)它來(lái)嘗試取消任務(wù)的執(zhí)行。
當(dāng)你在wrapperFuture上調(diào)用cancel()時(shí),它會(huì)將這個(gè)取消請(qǐng)求傳播給內(nèi)部包裝的originalFutureTask。
FutureTask.cancel()方法會(huì)檢查任務(wù)狀態(tài),如果任務(wù)還沒(méi)開(kāi)始跑,就將其狀態(tài)設(shè)置為CANCELLED。如果任務(wù)正在跑,它會(huì)根據(jù)你傳入的mayInterruptIfRunning參數(shù)來(lái)決定是否要中斷執(zhí)行該任務(wù)的線程。
import java.util.concurrent.*;
public class WrapperFutureCancelDemo {
public static void main(String[] args) throws InterruptedException {
FutureTask<String> originalFutureTask = new FutureTask<>(() -> {
System.out.println("子線程: 任務(wù)開(kāi)始,準(zhǔn)備睡5秒...");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// cancel(true) 會(huì)讓線程在這里拋出中斷異常
System.out.println("子線程: 我被中斷了,任務(wù)提前結(jié)束!");
return "未完成的結(jié)果";
}
System.out.println("子線程: 任務(wù)正常完成。");
return "已完成的結(jié)果";
});
ExecutorService executor = Executors.newSingleThreadExecutor();
// 提交后,我們只能通過(guò) wrapperFuture 來(lái)控制這個(gè)任務(wù)
Future<?> wrapperFuture = executor.submit(originalFutureTask);
// 讓主線程睡1秒,確保子任務(wù)已經(jīng)開(kāi)始運(yùn)行
Thread.sleep(1000);
System.out.println("主線程: 任務(wù)已經(jīng)跑了1秒,現(xiàn)在決定取消它。");
// 調(diào)用 wrapperFuture 的 cancel(true) 來(lái)中斷任務(wù)
boolean cancelled = wrapperFuture.cancel(true); // true表示如果任務(wù)正在運(yùn)行,就中斷它
System.out.println("主線程: 任務(wù)取消成功了嗎? " + cancelled);
// isCancelled() 也會(huì)被傳播
System.out.println("主線程: wrapperFuture 的狀態(tài)是否是已取消? " + wrapperFuture.isCancelled());
System.out.println("主線程: originalFutureTask 的狀態(tài)是否是已取消? " + originalFutureTask.isCancelled());
executor.shutdown();
}
}
運(yùn)行結(jié)果:
子線程: 任務(wù)開(kāi)始,準(zhǔn)備睡5秒...
主線程: 任務(wù)已經(jīng)跑了1秒,現(xiàn)在決定取消它。
子線程: 我被中斷了,任務(wù)提前結(jié)束!
主線程: 任務(wù)取消成功了嗎? true
主線程: wrapperFuture 的狀態(tài)是否是已取消? true
主線程: originalFutureTask 的狀態(tài)是否是已取消? true
在這個(gè)例子中,完全沒(méi)有用到get(),但wrapperFuture.cancel()成功地管理了任務(wù)的生命周期。
雖然wrapperFuture.get()返回的是null,但它依然是一個(gè)阻塞方法。它的作用是讓當(dāng)前線程等待,直到任務(wù)執(zhí)行完成。這是一個(gè)非常重要的同步機(jī)制。
更重要的是,如果你的原始任務(wù)在執(zhí)行時(shí)拋出了異常,調(diào)用wrapperFuture.get()會(huì)重新拋出這個(gè)異常(包裝在ExecutionException中)。這使得主線程能夠捕獲并處理后臺(tái)任務(wù)的錯(cuò)誤。
import java.util.concurrent.*;
public class WrapperFutureExceptionDemo {
public static void main(String[] args) {
FutureTask<String> originalFutureTask = new FutureTask<>(() -> {
System.out.println("子線程: 任務(wù)開(kāi)始,即將拋出異常!");
throw new RuntimeException("計(jì)算出錯(cuò)!");
});
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<?> wrapperFuture = executor.submit(originalFutureTask);
try {
System.out.println("主線程: 調(diào)用 wrapperFuture.get() 等待任務(wù)完成...");
// 這里會(huì)阻塞,直到任務(wù)結(jié)束。因?yàn)槿蝿?wù)拋了異常,get()也會(huì)拋出異常。
wrapperFuture.get();
} catch (InterruptedException e) {
System.err.println("主線程: 等待時(shí)被中斷了。");
} catch (ExecutionException e) {
System.err.println("主線程: 成功捕獲到后臺(tái)任務(wù)的異常!");
System.err.println(" - 根本原因: " + e.getCause());
}
executor.shutdown();
}
}
運(yùn)行結(jié)果:
子線程: 任務(wù)開(kāi)始,即將拋出異常!
主線程: 調(diào)用 wrapperFuture.get() 等待任務(wù)完成...
主線程: 成功捕獲到后臺(tái)任務(wù)的異常!
- 根本原因: java.lang.RuntimeException: 計(jì)算出錯(cuò)!
即便不關(guān)心返回值,wrapperFuture.get()在錯(cuò)誤處理和流程同步上也是不可或缺的。
ExecutorService的設(shè)計(jì)哲學(xué)是提供一套統(tǒng)一、可預(yù)測(cè)的接口。
submit(Callable<T> task)返回Future<T>submit(Runnable task, T result)返回Future<T>submit(Runnable task)返回Future<?>
無(wú)論你提交什么類(lèi)型的任務(wù),submit方法總是返回一個(gè)Future對(duì)象。這讓使用者可以依賴(lài)一個(gè)統(tǒng)一的模型來(lái)管理所有異步任務(wù),而不必寫(xiě)if-else來(lái)判斷提交的是Callable還是Runnable。wrapperFuture (Future<?>)正是這個(gè)統(tǒng)一模型中,用于代表“無(wú)返回值任務(wù)”的那個(gè)標(biāo)準(zhǔn)占位符。
總結(jié)來(lái)說(shuō),新返回的Future對(duì)象能夠安全、統(tǒng)一地管理后臺(tái)任務(wù),即便并不關(guān)心那個(gè)任務(wù)的返回值。
總結(jié)
到此這篇關(guān)于Java開(kāi)發(fā)異步編程中常用的接口和類(lèi)的文章就介紹到這了,更多相關(guān)Java異步編程接口和類(lèi)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java進(jìn)階之在Word文檔中動(dòng)態(tài)增刪頁(yè)面的完整指南
本文將介紹如何使用一款第三方 Java 庫(kù),以編程方式實(shí)現(xiàn)對(duì) Word 文檔頁(yè)面及段落內(nèi)容的精細(xì)化管理,文章將從環(huán)境配置開(kāi)始,逐步演示如何新增頁(yè)面,在指定位置插入頁(yè)面以及刪除特定頁(yè)面,希望對(duì)大家有一定的幫助2026-05-05
Spring boot 使用JdbcTemplate訪問(wèn)數(shù)據(jù)庫(kù)
SpringBoot 是為了簡(jiǎn)化 Spring 應(yīng)用的創(chuàng)建、運(yùn)行、調(diào)試、部署等一系列問(wèn)題而誕生的產(chǎn)物。本文重點(diǎn)給大家介紹spring boot 使用JdbcTemplate訪問(wèn)數(shù)據(jù)庫(kù),需要的朋友可以參考下2018-05-05
SpringBoot教程_創(chuàng)建第一個(gè)SpringBoot項(xiàng)目
這篇文章主要介紹了SpringBoot教程_創(chuàng)建第一個(gè)SpringBoot項(xiàng)目,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06

