线程池的execute与submit的区别?当线程池的线程运行错误时又是如何处理的?

211 阅读3分钟

为了方便演示,这里使用 Executors 创建线程池。然而在实际开发中,强烈建议不要使用 Executors 来创建线程池。因为 Executors 提供的方法有一些默认配置,可能会导致一些潜在的问题。相反,应该使用 ThreadPoolExecutor 类来手动配置和创建线程池,以确保线程池的行为符合实际需求,并避免潜在的风险。

使用execute方法

public static void main(String[] args) {
    ExecutorService executorService = Executors.newFixedThreadPool(3);
    testExecute(executorService);
}

private static void testExecute(ExecutorService executorService) {
    executorService.execute(() -> {
        System.out.println("执行线程池execute方法");
        throw new RuntimeException("execute方法抛出异常");
    });
}

image.png

可以看到,执行 execute 方法,当任务抛出异常时,当前线程会终止,且会打印异常信息。

使用submit

public static void main(String[] args) {
    ExecutorService executorService = Executors.newFixedThreadPool(3);
    testSubmit1(executorService);
    testSubmit2(executorService);
}

private static void testSubmit1(ExecutorService executorService) {
    executorService.submit(() -> {
        System.out.println("执行线程池submit方法1");
        throw new RuntimeException("submit方法1抛出异常");
    });
}

private static void testSubmit2(ExecutorService executorService) throws Exception {
    Future<Object> feature = executorService.submit(() -> {
        System.out.println("执行线程池submit方法2");
        throw new RuntimeException("submit方法2抛出异常");
    });
    feature.get();
}

image.png

可以看到,执行 submit 方法,当任务抛出异常时,当前线程不会终止,不会打印异常信息,只有在调用 get 方法的时候,才会打印异常信息。

线程池异常处理的底层原理

image.png

image.png

线程池的<T> Future<T> submit(Callable<T> task)方法最终会调void execute(Runnable command)方法执行任务,只不过传的是 RunnableFuture 对象。而直接请求void execute(Runnable command)方法传的是 Runnable 对象。

newTaskFor 方法创建了一个 FutureTask 对象。FutureTask 实现了 RunnableFuture 接口,所以它也会实现 run 方法。

image.png

传给 submit 方法的参数是一个 Callable 对象,Callable 的 call 方法被 try-catch 包住了,且在 catch 方法中没有抛出异常。call 的异常被 FutureTask 吞掉了,所以执行线程池的 submit 方法不会抛出异常。

当线程池线程出现异常,submit与execute的不同处理方式

submit提交方式

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        submitWork(executorService);
        submitWork(executorService);
        submitWork(executorService);
        Thread.sleep(4000);
        Future<?> future = executorService.submit(() -> {
            System.out.println(Thread.currentThread().getName() + "============测试");
        });
    }
    
    private static void submitWork(ExecutorService executorService) throws ExecutionException, InterruptedException {
        Future<?> future = executorService.submit(() -> {
            for (int i = 0; i < 2; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + "========" + i);
                    Thread.sleep(2000);
                    int num = i/0;
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        try{
            future.get();
        }catch (Exception e){
            System.out.println("出现异常了!");
        }
    }

image.png

可以看到,使用submit提交任务时,即使线程出现异常,也会替换线程池中的线程

execute提交方式

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        executeWork(executorService);
        executeWork(executorService);
        executeWork(executorService);
        Thread.sleep(4000);
        executorService.execute(()->{
            System.out.println(Thread.currentThread().getName() + "===============测试");
        });
    }
    private static void executeWork(ExecutorService executorService) throws ExecutionException, InterruptedException {
        executorService.execute(() -> {
            for (int i = 0; i < 2; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + "========" + i);
                    Thread.sleep(2000);
                    int num = i/0;
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
    }

image.png

可以看到使用execute提交方式后,线程池中的线程被替换了,使用了一个新线程来执行任务

总结

当一个线程池里面的线程异常后:

1、当执行方式是execute时,可以看到堆栈异常的输出
原因:ThreadPoolExecutor.runWorker()方法中,task.run(),即执行我们的方法,如果异常的话会throw x;所以可以看到异常。

2、当执行方式是submit时,堆栈异常没有输出。但是调用Future.get()方法时,可以捕获到异常
原因:ThreadPoolExecutor.runWorker()方法中,task.run(),其实还会继续执行FutureTask.run()方法,再在此方法中c.call()调用我们的方法,
如果报错是setException(),并没有抛出异常。当我们去get()时,会将异常抛出。

3、线程池里面当一个线程出现异常后,不会影响其他线程的运行

4、线程池会根据提交任务方式不同来决定是否替换线程

  • execute   会删除当前线程,重新新建线程

  • submit    线程不会发生变化