行业资讯 2025年08月6日
0 收藏 0 点赞 130 浏览 5669 个字
摘要 :

文章目录 一、方法定义与功能 提交Callable任务获取执行结果 提交Runnable任务并指定返回结果 提交Runnable任务不获取返回值 二、异常处理的差异 execute()的异常处……




  • 一、方法定义与功能
    • 提交Callable任务获取执行结果
    • 提交Runnable任务并指定返回结果
    • 提交Runnable任务不获取返回值
  • 二、异常处理的差异
  • 三、总结

Java多线程编程submit()execute()是两个常用的向线程池提交任务的方法。这两个方法虽然功能相近,但在很多方面存在差异。下面咱们就详细分析一下它们的不同之处。

一、方法定义与功能

execute()方法定义在Executor接口中,它只能提交Runnable类型的任务,并且没有返回值,这意味着我们没办法通过它获取任务的执行结果。要是任务执行过程中抛出异常,会由线程池的未捕获异常处理器来处理。来看一下它的源码:

public interface Executor {
    /**
     * 在未来某个时间执行给定的命令。该命令可能在新线程、线程池中的线程或调用线程中执行,具体取决于{@code Executor}的实现。
     *
     * @param command 可运行的任务
     * @throws RejectedExecutionException 如果此任务无法被接受执行
     * @throws NullPointerException 如果command为null
     */
    void execute(Runnable command);
}

submit()方法则定义在ExecutorService接口中,这个接口继承自Executor接口。submit()方法功能更强大一些,它既可以提交Runnable任务,也能提交Callable任务,并且会返回一个Future对象。通过这个Future对象,我们能获取任务的执行结果,还能取消任务。对于Runnable任务,任务完成后返回的Future对象会是null,而任务执行时的异常会被捕获并存储在Future中,只有调用Future.get()方法时才会抛出异常。下面是它的源码:

// ExecutorService继承Executor
public interface ExecutorService extends Executor {
    /**
     * 提交一个有返回值的任务用于执行,并返回一个表示任务待完成结果的Future。Future的{@code get}方法将在任务成功完成时返回任务的结果。
     *
     * <p>
     * 如果你想立即阻塞等待任务完成,可以使用{@code result = exec.submit(aCallable).get();}这种形式的代码。
     *
     * <p>注意:{@link Executors}类包含一组方法,可以将其他一些常见的类似闭包的对象,例如{@link java.security.PrivilegedAction}转换为{@link Callable}形式,以便提交。
     *
     * @param task 要提交的任务
     * @param <T> 任务结果的类型
     * @return 一个表示任务待完成的Future
     * @throws RejectedExecutionException 如果任务无法被调度执行
     * @throws NullPointerException 如果任务为null
     */
    <T> Future<T> submit(Callable<T> task);

    /**
     * 提交一个Runnable任务用于执行,并返回一个表示该任务的Future。Future的{@code get}方法将在任务成功完成时返回给定的结果。
     *
     * @param task 要提交的任务
     * @param result 要返回的结果
     * @param <T> 结果的类型
     * @return 一个表示任务待完成的Future
     * @throws RejectedExecutionException 如果任务无法被调度执行
     * @throws NullPointerException 如果任务为null
     */
    <T> Future<T> submit(Runnable task, T result);

    /**
     * 提交一个Runnable任务用于执行,并返回一个表示该任务的Future。Future的{@code get}方法将在任务成功完成时返回{@code null}。
     *
     * @param task 要提交的任务
     * @return 一个表示任务待完成的Future
     * @throws RejectedExecutionException 如果任务无法被调度执行
     * @throws NullPointerException 如果任务为null
     */
    Future<?> submit(Runnable task);
}

submit()方法有三种重载形式,下面分别介绍一下:

提交Callable任务获取执行结果

<T> Future<T> submit(Callable<T> task);

使用这个方法提交Callable任务时,它会返回该线程的执行结果。示例代码如下:

Future<String> future = threadPool.submit(() -> {
    Thread.sleep(1000);
    return \"Hello from Callable!\";
});
System.out.println(future.get()); // 输出 \"Hello from Callable!\"

在这段代码里,submit()方法提交了一个Callable任务,任务执行完成后,通过future.get()就能获取到任务返回的字符串结果。

提交Runnable任务并指定返回结果

Future<T> submit(Runnable task, T result);

这个方法用于提交Runnable任务,同时可以指定任务执行后的返回结果。我们知道Runnable本身是没有返回值的,但有时候我们希望有个标识来判断程序是否执行完毕,就可以用这个方法指定返回值。示例如下:

Future<String> future = threadPool.submit(
    () -> {
        try {
            System.out.println(\"Processing data...\");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    },
    \"SUCCESS\" // 任务完成后返回这个字符串
);

System.out.println(future.get()); // 输出 \"SUCCESS\"(而不是 null)

假设存在一个不需要返回计算结果的任务(Runnable类型),但我们又希望Future能返回一个状态信息(比如\"SUCCESS\"\"FAILED\"),而不是null,这时就可以使用这个方法。

提交Runnable任务不获取返回值

Future<?> submit(Runnable task);

这种形式提交的Runnable任务没有返回值。示例代码如下:

Future<?> future = threadPool.submit(() -> {
    System.out.println(\"Running a Runnable task\");
});
future.get(); // 返回 null

二、异常处理的差异

execute()的异常处理

当使用execute()提交任务并执行线程时,如果任务中抛出异常且没有被捕获,异常会直接打印在控制台。例如下面这段代码:

public class ExecuteUncaughtException {
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newSingleThreadExecutor();

        threadPool.execute(() -> {
            System.out.println(\"任务开始执行...\");
            throw new RuntimeException(\"这是一个未捕获的异常!\"); // 未捕获的异常
        });

        // 后续任务可能不会执行(因为线程可能已终止)
        threadPool.execute(() -> System.out.println(\"后续任务\"));

        threadPool.shutdown();
    }
}

输出结果为:

任务开始执行...
Exception in thread \"pool-1-thread-1\" java.lang.RuntimeException: 这是一个未捕获的异常!
    at ExecuteUncaughtException.lambda$main$0(ExecuteUncaughtException.java:10)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)

从这个例子可以看出,execute()处理异常存在一些缺点:异常如果未被捕获,会直接抛出并打印到控制台;而且线程池中的线程可能因为异常而终止,特别是在newSingleThreadExecutor这种只有一个线程的情况下,会导致后续任务无法执行。

submit()的异常处理

使用submit()提交任务并执行线程时,发生的异常会被存储在FutureTask中,只有当调用ft.get()时,这个异常才会被抛出。示例代码如下:

import java.util.concurrent.*;

public class SubmitUncaughtException {
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newSingleThreadExecutor();

        Future<?> future = threadPool.submit(() -> {
            System.out.println(\"任务开始执行...\");
            throw new RuntimeException(\"这是一个未捕获的异常!\");
        });

        try {
            //使用future.get,就在编译时必须处理异常
            future.get(); // 在这里抛出 ExecutionException
        } catch (ExecutionException e) {
            System.err.println(\"捕获到 Future 中的异常: \" + e.getCause()); // 获取原始异常
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }

        // 后续任务正常执行
        threadPool.execute(() -> System.out.println(\"后续任务\"));

        threadPool.shutdown();
    }
}

输出结果为:

任务开始执行...
捕获到 Future 中的异常: java.lang.RuntimeException: 这是一个未捕获的异常!
后续任务

从这里可以看到,要想让异常不影响线程的执行,需要使用try-catch来处理异常。

如果在execute()中也捕获异常,情况会怎样呢?来看下面的代码:

ExecutorService threadPool = Executors.newSingleThreadExecutor();

threadPool.execute(() -> {
    try {
        System.out.println(\"任务开始执行...\");
        throw new RuntimeException(\"这是一个捕获的异常!\");
    } catch (RuntimeException e) {
        System.err.println(\"捕获到异常: \" + e.getMessage()); // 捕获并处理
    }
});

// 后续任务正常执行
threadPool.execute(() -> System.out.println(\"后续任务\"));

threadPool.shutdown();

输出结果为:

任务开始执行...
捕获到异常: 这是一个捕获的异常!
后续任务

从结果可以看出,当在execute()中捕获并处理异常时,和submit()的执行过程是一样的。

那么为什么很多人说submit()处理异常更强大呢?主要有两个原因:
第一,使用submit()执行线程时,通过future.get()获取执行结果需要显式地使用try-catch处理异常(因为future.get()会抛出受检异常,不使用try-catch在编译时就会报错)。不过,对于有良好编程习惯且熟练使用execute()的人来说,在execute()的线程中使用try-catch也能达到类似效果,所以这一点优势并不明显。
第二,也是submit()真正强大的地方,execute()的异常信息无法向上传递,当execute()的异常被消化后,调用方完全不知道任务失败了(除非手动记录日志)。而submit()的异常可以通过Future.get()传递给调用方,这种方式更适合需要统一错误处理的场景。

三、总结

在实际开发中,我们经常会用到ExecutorService接口,因为它继承了Executor接口,所以既可以使用execute()方法,也能使用submit()方法。不过综合来看,submit()方法更强大一些,它支持返回值(即使是Runnable任务也能有预期的返回值),并且在异常处理方面表现更好。而execute()如果在Runnable任务中抛出未捕获的异常,异常会直接传播到线程池的未捕获异常处理器(默认打印堆栈,但程序不会停止),但线程会因异常退出,这可能导致线程池中的线程减少,进而影响后续任务的执行。所以,在选择使用哪个方法时,需要根据具体的业务需求来决定。

微信扫一扫

支付宝扫一扫

版权: 转载请注明出处:https://www.zuozi.net/10503.html

管理员

相关推荐
2025-08-06

文章目录 一、Reader 接口概述 1.1 什么是 Reader 接口? 1.2 Reader 与 InputStream 的区别 1.3 …

988
2025-08-06

文章目录 一、事件溯源 (一)核心概念 (二)Kafka与Golang的优势 (三)完整代码实现 二、命令…

465
2025-08-06

文章目录 一、证明GC期间执行native函数的线程仍在运行 二、native线程操作Java对象的影响及处理方…

348
2025-08-06

文章目录 一、事务基础概念 二、MyBatis事务管理机制 (一)JDBC原生事务管理(JdbcTransaction)…

456
2025-08-06

文章目录 一、SnowFlake算法核心原理 二、SnowFlake算法工作流程详解 三、SnowFlake算法的Java代码…

517
2025-08-06

文章目录 一、本地Jar包的加载操作 二、本地Class的加载方法 三、远程Jar包的加载方式 你知道Groo…

832
发表评论
暂无评论

还没有评论呢,快来抢沙发~

助力内容变现

将您的收入提升到一个新的水平

点击联系客服

在线时间:08:00-23:00

客服QQ

122325244

客服电话

400-888-8888

客服邮箱

122325244@qq.com

扫描二维码

关注微信客服号