Java線程池中execute()和submit()兩個方法的區(qū)別(源碼&實戰(zhàn)全解析)
前言
在 Java 并發(fā)編程中,線程池是核心技術(shù)之一,而 execute() 和 submit() 是線程池最常用的兩個方法。很多開發(fā)者只停留在表面認(rèn)識——execute 拋異常,submit 返回 Future,但這種理解遠(yuǎn)遠(yuǎn)不夠。
本文將從源碼層面深度解析這兩個方法的本質(zhì)差異,并通過實戰(zhàn)案例演示它們的適用場景。
一、核心差異一覽
| 維度 | execute() | submit() |
|---|---|---|
| 返回值 | void | Future |
| 異常傳播 | 任務(wù)內(nèi)異常會直接拋出到 UncaughtExceptionHandler,主線程無法感知 | 異常被 FutureTask 捕獲并存儲,調(diào)用 get() 時才拋出 ExecutionException |
| 任務(wù)類型 | 僅支持 Runnable | 支持 Runnable 和 Callable |
| 適用場景 | 不關(guān)心結(jié)果的異步任務(wù)(如日志發(fā)送、數(shù)據(jù)清理) | 需要獲取結(jié)果或處理異常的任務(wù)(如計算、RPC 調(diào)用) |
| 接口定義 | Executor 接口 | ExecutorService 接口 |
二、源碼層面解析
2.1 submit() 的源碼實現(xiàn)
// AbstractExecutorService.java
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
// 關(guān)鍵點1:將 Runnable 包裝為 RunnableFuture
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask); // 關(guān)鍵點2:最終還是調(diào)用 execute()
return ftask; // 關(guān)鍵點3:返回 Future 對象
}
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
核心洞察:submit() 本質(zhì)上是 execute() 的包裝器,它在調(diào)用 execute() 前做了兩件事:
- 任務(wù)包裝:將 Runnable/Callable 包裝成 FutureTask
- 返回句柄:給調(diào)用者一個 Future 對象用于獲取結(jié)果
2.2 execute() 的核心邏輯
// ThreadPoolExecutor.java
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
// 1. workerCount < corePoolSize -> 創(chuàng)建核心線程
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
// 2. workerCount >= corePoolSize && workQueue 未滿 -> 入隊
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (!isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 3. workerCount >= corePoolSize && workQueue 已滿 -> 創(chuàng)建非核心線程
else if (!addWorker(command, false))
// 4. 都失敗 -> 拒絕策略
reject(command);
}
執(zhí)行流程:
- 工作線程數(shù) < 核心線程數(shù) → 創(chuàng)建核心線程執(zhí)行
- 工作線程數(shù) ≥ 核心線程數(shù),隊列未滿 → 任務(wù)入隊
- 工作線程數(shù) ≥ 核心線程數(shù),隊列已滿 → 創(chuàng)建非核心線程
- 都失敗 → 觸發(fā)拒絕策略
三、實戰(zhàn)場景對比
3.1 異常處理的根本差異
execute() 的異常陷阱:
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.execute(() -> {
throw new RuntimeException("任務(wù)異常");
});
// 主線程無法捕獲這個異常!
// 異常會直接拋出到線程池的 UncaughtExceptionHandler
submit() 的異常安全:
ExecutorService executor = Executors.newFixedThreadPool(2);
Future<?> future = executor.submit(() -> {
throw new RuntimeException("任務(wù)異常");
});
try {
future.get(); // 調(diào)用 get() 時才會拋出 ExecutionException
} catch (ExecutionException e) {
System.out.println("捕獲到任務(wù)異常: " + e.getCause());
}
3.2 批量任務(wù)處理 - submit 優(yōu)勢場景
ExecutorService executor = Executors.newFixedThreadPool(4);
List<Future<Integer>> futures = new ArrayList<>();
// 提交多個任務(wù)
for (int i = 0; i < 10; i++) {
final int num = i;
futures.add(executor.submit(() -> compute(num)));
}
// 批量獲取結(jié)果
for (Future<Integer> future : futures) {
try {
System.out.println(future.get());
} catch (Exception e) {
System.out.println("任務(wù)執(zhí)行異常: " + e.getCause());
}
}
private int compute(int num) {
// 模擬計算任務(wù)
return num * num;
}
3.3 超時控制 - submit 獨有能力
ExecutorService executor = Executors.newFixedThreadPool(2);
Future<String> future = executor.submit(() -> {
Thread.sleep(5000);
return "結(jié)果";
});
try {
String result = future.get(2, TimeUnit.SECONDS); // 2秒超時
System.out.println(result);
} catch (TimeoutException e) {
future.cancel(true); // 中斷任務(wù)
System.out.println("任務(wù)超時,已取消");
}
3.4 execute 的最佳實踐 - 異常監(jiān)控
// 設(shè)置全局異常處理器
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
System.out.println("線程 " + t.getName() + " 發(fā)生異常: " + e);
});
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.execute(() -> {
throw new RuntimeException("異常會被 UncaughtExceptionHandler 捕獲");
});
四、性能考量
- execute():略輕量,直接提交任務(wù),無需創(chuàng)建 FutureTask 對象
- submit():因創(chuàng)建 FutureTask 有極小開銷,但在實際業(yè)務(wù)中差異可忽略
- 建議:如果不需要返回值和異常處理,優(yōu)先使用 execute()
五、面試標(biāo)準(zhǔn)答案
問題: Java 線程池中 execute() 和 submit() 有什么區(qū)別?
標(biāo)準(zhǔn)回答:
核心差異:execute() 是 Executor 接口定義的基礎(chǔ)方法,用于提交不需要返回值的任務(wù);submit() 是 ExecutorService 擴(kuò)展的方法,可以提交 Callable/Runnable 并返回 Future 對象。
源碼層面:submit() 內(nèi)部將任務(wù)包裝成 FutureTask,然后調(diào)用 execute() 執(zhí)行,所以 execute() 是 submit() 的底層實現(xiàn)。
異常處理:這是最重要的區(qū)別——execute() 中任務(wù)的異常會直接拋出到線程池的異常處理器,主線程無法感知;submit() 中任務(wù)的異常被 FutureTask 捕獲存儲,只有調(diào)用 Future.get() 時才會拋出 ExecutionException,主線程可以統(tǒng)一處理。
適用場景:execute() 適合"提交即忘"的異步任務(wù)(如日志、清理);submit() 適合需要結(jié)果、超時控制或細(xì)粒度異常處理的任務(wù)。
性能考量:execute() 略輕量,submit() 因為創(chuàng)建 FutureTask 有極小開銷,但在實際業(yè)務(wù)中差異可忽略。
六、進(jìn)階思考
6.1 為什么 submit() 要返回 Future?
這是"控制權(quán)"的設(shè)計哲學(xué),調(diào)用者可以通過 Future 實現(xiàn)取消、超時、結(jié)果獲取等精細(xì)控制。
6.2 線程池的拒絕策略對兩者有區(qū)別嗎?
沒有,最終都是調(diào)用 execute(),拒絕策略統(tǒng)一生效。
6.3 如何既用 execute() 的輕量,又實現(xiàn)異常監(jiān)控?
可以自定義 ThreadPoolExecutor,重寫 afterExecute() 方法:
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, 4, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>()
) {
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
if (t != null) {
System.out.println("任務(wù)執(zhí)行異常: " + t);
} else if (r instanceof Future<?>) {
try {
((Future<?>) r).get();
} catch (Exception e) {
System.out.println("Future 異常: " + e.getCause());
}
}
}
};
七、總結(jié)
這道題的深層考點是:是否理解 Java 并發(fā)框架中"任務(wù)"和"執(zhí)行"的分離設(shè)計,以及異常在不同線程上下文中的傳播機(jī)制。
- execute():輕量級異步執(zhí)行,適合"提交即忘"場景
- submit():功能完善,支持結(jié)果獲取、超時控制、異常統(tǒng)一處理
選擇建議:
- 不需要返回值 → 優(yōu)先 execute()
- 需要返回值或異常處理 → 必須使用 submit()
到此這篇關(guān)于Java線程池中execute()和submit()兩個方法區(qū)別的文章就介紹到這了,更多相關(guān)Java線程池execute()和submit()區(qū)別內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解 maven的pom.xml用<exclusion>解決版本問題
這篇文章主要介紹了詳解 maven的pom.xml用<exclusion>解決版本問題的相關(guān)資料,希望通過本文能幫助到大家,需要的朋友可以參考下2017-09-09
SpringBoot?整合Security權(quán)限控制的初步配置
這篇文章主要為大家介紹了SpringBoot?整合Security權(quán)限控制的初步配置實例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11
javaweb中Filter(過濾器)的常見應(yīng)用
這篇文章主要介紹了javaweb中Filter的常見應(yīng)用,過濾器的使用方法,感興趣的小伙伴們可以參考一下2015-12-12
Java編程實現(xiàn)深度優(yōu)先遍歷與連通分量代碼示例
這篇文章主要介紹了Java編程實現(xiàn)深度優(yōu)先遍歷與連通分量代碼示例,2017-11-11
Java的方法和this關(guān)鍵字如何理解與應(yīng)用
Java語言中的“方法”(Method)在其他語言當(dāng)中也可能被稱為“函數(shù)”(Function)。對于一些復(fù)雜的代碼邏輯,如果希望重復(fù)使用這些代碼,并且做到“隨時任意使用”,那么就可以將這些代碼放在一個大括號{}當(dāng)中,并且起一個名字。使用代碼的時候,直接找到名字調(diào)用即可2021-10-10
AndroidHttpClient使用Cookie應(yīng)用分析
今天想把一個用使用了HttpClient的自動簽到小程序移植到Android上,還好Android的SDK自帶了HttpClient的包.當(dāng)然也可以繼續(xù)使用DefaultHttpClient,但用為Android定制的AndroidHttpClient自然更好2012-11-11

