为了方便演示,这里使用 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方法抛出异常");
});
}
可以看到,执行 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();
}
可以看到,执行 submit 方法,当任务抛出异常时,当前线程不会终止,不会打印异常信息,只有在调用 get 方法的时候,才会打印异常信息。
线程池异常处理的底层原理
线程池的<T> Future<T> submit(Callable<T> task)方法最终会调void execute(Runnable command)方法执行任务,只不过传的是 RunnableFuture 对象。而直接请求void execute(Runnable command)方法传的是 Runnable 对象。
newTaskFor 方法创建了一个 FutureTask 对象。FutureTask 实现了 RunnableFuture 接口,所以它也会实现 run 方法。
传给 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("出现异常了!");
}
}
可以看到,使用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);
}
}
});
}
可以看到使用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 线程不会发生变化