在上一篇文章中,我们介绍了Executor.execute方法,了解了execute方法的作用及其实现,但是在实际业务中,较少直接用execute方法,而是用Executor的子接口ExecutorService中的submit方法。
1 submit方法 vs. execute方法
它们有以下关键区别:
a. 返回值
| 方法 | 返回值类型 | 说明 |
|---|---|---|
execute(Runnable) | void | 无返回值 |
submit(Runnable) | Future<?> | 返回一个 Future 对象(结果为 null) |
submit(Callable<T>) | Future<T> | 返回一个包含实际结果的 Future 对象 |
b. 支持的任务类型
| 方法 | 支持的任务类型 |
|---|---|
execute() | 仅 Runnable |
submit() | Runnable 和 Callable |
c. 异常处理
| 方法 | 异常处理机制 |
|---|---|
execute() | 任务中的异常会直接抛出到线程池,可能终止线程 |
submit() | 异常被封装在 Future 中,需通过 Future.get() 捕获 |
典型代码对比如下
使用 execute()
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.execute(() -> {
System.out.println("任务执行中");
// 如果这里抛出异常,线程可能终止
});
使用 submit()
ExecutorService executor = Executors.newFixedThreadPool(2);
Future<?> future = executor.submit(() -> {
System.out.println("任务执行中");
return "结果"; // 可以是 Callable 的返回值
});
// 获取结果(或捕获异常)
try {
String result = (String) future.get();
} catch (ExecutionException e) {
// 处理任务中的异常
e.getCause().printStackTrace();
}
所以在实际应用中如何选择呢?
- 用
execute():当任务无需返回值且不关心异常细节时(例如日志记录、异步通知)。 - 用
submit():当需要以下功能时:- 获取任务结果(
Callable) - 捕获任务中的异常
- 取消任务(
future.cancel(true)) - 判断任务是否完成(
future.isDone())
- 获取任务结果(
2 submit方法分析
submit方法是ExecutorService接口的关键方法,用于提交任务到线程池并返回一个Future对象。以下是对submit方法源码实现的详细解析:
2.1 方法定义与重载
ExecutorService接口定义了三个submit方法重载:
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
这些方法允许提交Runnable或Callable任务,并返回Future对象以跟踪任务状态和结果。
2.2 源码实现(以AbstractExecutorService为例)
AbstractExecutorService是ExecutorService的抽象实现类,提供了submit方法的默认实现。
示例:submit(Runnable task)
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
关键步骤解析:
- 任务封装:通过
newTaskFor将Runnable或Callable封装为RunnableFuture(即FutureTask)。 - 任务提交:调用线程池的
execute(ftask)方法将任务加入执行队列。可以看到,这里其实还是调用了execute方法,所以submit方法其实是对execute方法的包装和增强。 - 返回
Future:返回FutureTask对象,用于后续结果获取或任务取消。
newTaskFor方法将任务包装成FutureTask,它是RunnableFuture的实现类,兼具Runnable和Future的功能。
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
因为任务分为Runable和Callable两种,所以封装方法也是多态的,具体实现如下
Runnable封装:传入Runnable和一个默认结果value(通常为null),最终通过Callable适配器RunnableAdapter转换为Callable。Callable直接封装:直接使用Callable的逻辑。
| Runable | Callable |
|---|---|
FutureTask是submit方法的核心,负责任务执行、状态管理和结果存储。
下面对FutureTask进行分析
状态管理
FutureTask维护以下状态(通过volatile int state):
NEW:初始状态。COMPLETING:任务执行中。NORMAL:任务正常完成。EXCEPTIONAL:任务抛出异常。CANCELLED:任务被取消。INTERRUPTING:中断中。INTERRUPTED:已中断。
执行流程
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
- 线程池调用
FutureTask.run()。 - 执行内部
Callable或适配后的Runnable逻辑。 - 结果存储:
- 成功:调用
set(result)保存结果。 - 异常:调用
setException(ex)保存异常。
- 成功:调用
- 唤醒所有等待结果的线程(通过
LockSupport.unpark)。
可以看到, submit()提交的任务,异常被封装在Future中,调用Future.get()时抛出ExecutionException,需通过e.getCause()获取原始异常。
关键设计思想
- 统一任务抽象:通过
FutureTask将Runnable和Callable统一为可管理的异步任务。 - 状态与结果隔离:
FutureTask通过状态机管理任务生命周期,确保线程安全。 - 资源控制:线程池通过队列和拒绝策略防止资源耗尽。
总结
submit方法通过封装任务为FutureTask,封装过程中也会把Runable包装成为Callable类型;- 结合线程池的调度机制,实现了异步任务的提交、执行和结果管理:执行的时候就会执行FutureTask的run方法,在run方法中,会调用callable.call方法,这里面会执行最原始任务的run方法,并返回result(如果原始任务是Runable,则result是null,若原始任务是Callable,则result为对应的泛型);