java线程池的submit方法分析

588 阅读4分钟

在上一篇文章中,我们介绍了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()RunnableCallable

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);

这些方法允许提交RunnableCallable任务,并返回Future对象以跟踪任务状态和结果。

2.2 源码实现(以AbstractExecutorService为例)

AbstractExecutorServiceExecutorService的抽象实现类,提供了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;
}

关键步骤解析

  1. 任务封装:通过newTaskForRunnableCallable封装为RunnableFuture(即FutureTask)。
  2. 任务提交:调用线程池的execute(ftask)方法将任务加入执行队列。可以看到,这里其实还是调用了execute方法,所以submit方法其实是对execute方法的包装和增强。
  3. 返回Future:返回FutureTask对象,用于后续结果获取或任务取消。

newTaskFor方法将任务包装成FutureTask,它是RunnableFuture的实现类,兼具RunnableFuture的功能。

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的逻辑。
RunableCallable

FutureTasksubmit方法的核心,负责任务执行、状态管理和结果存储。

下面对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);
    }
}
  1. 线程池调用FutureTask.run()
  2. 执行内部Callable或适配后的Runnable逻辑。
  3. 结果存储:
    • 成功:调用set(result)保存结果。
    • 异常:调用setException(ex)保存异常。
  4. 唤醒所有等待结果的线程(通过LockSupport.unpark)。

可以看到, submit()提交的任务,异常被封装在Future中,调用Future.get()时抛出ExecutionException,需通过e.getCause()获取原始异常。

关键设计思想

  • 统一任务抽象:通过FutureTaskRunnableCallable统一为可管理的异步任务。
  • 状态与结果隔离FutureTask通过状态机管理任务生命周期,确保线程安全。
  • 资源控制:线程池通过队列和拒绝策略防止资源耗尽。

总结

  1. submit方法通过封装任务为FutureTask,封装过程中也会把Runable包装成为Callable类型;
  2. 结合线程池的调度机制,实现了异步任务的提交、执行和结果管理:执行的时候就会执行FutureTask的run方法,在run方法中,会调用callable.call方法,这里面会执行最原始任务的run方法,并返回result(如果原始任务是Runable,则result是null,若原始任务是Callable,则result为对应的泛型);