写点什么

面试官:线程池遇到未处理的异常会崩溃吗?

作者:王磊
  • 2024-09-13
    陕西
  • 本文字数:3452 字

    阅读完需:约 11 分钟

面试官:线程池遇到未处理的异常会崩溃吗?

首先,这个问题考察的是你对线程池 execute 方法和 submit 方法的理解,在 Java 线程池的使用中,我们可以通过 execute 方法或 submit 方法给线程池添加任务,但如果线程池中的程序在执行时,遇到了未处理的异常会怎么呢?接下来我们一起来看。

1.execute 方法

execute 方法用于提交一个不需要返回值的任务给线程池执行,它接收一个 Runnable 类型的参数,并且不返回任何结果。


它的使用示例代码如下:


import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;
public class ExecuteDemo { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(5); // 使用 execute 方法提交任务 executor.execute(new Runnable() { @Override public void run() { System.out.println("Task running in " + Thread.currentThread().getName()); try { // 模拟任务执行 Thread.sleep(2000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); System.err.println("Task was interrupted"); } System.out.println("Task finished"); } });
// 关闭线程池 executor.shutdown(); }}
复制代码

2.submit 方法

submit 方法用于提交一个需要返回值的任务(Callable 对象),或者不需要返回值但希望获取任务状态的任务(Runnable 对象,但会返回一个 Future 对象)。


它接收一个 Callable 或 Runnable 类型的参数,并返回一个 Future 对象,通过该对象可以获取任务的执行结果或检查任务的状态。

2.1 提交 Callable 任务

示例代码如下:


import java.util.concurrent.Callable;  import java.util.concurrent.ExecutionException;  import java.util.concurrent.ExecutorService;  import java.util.concurrent.Executors;  import java.util.concurrent.Future;    public class SubmitCallableDemo {      public static void main(String[] args) {          // 创建一个固定大小的线程池          ExecutorService executorService = Executors.newFixedThreadPool(2);            // 提交一个 Callable 任务给线程池执行          Future<String> future = executorService.submit(new Callable<String>() {              @Override              public String call() throws Exception {                  Thread.sleep(2000); // 模拟任务执行时间                  return "Task's execution result";              }          });            try {              // 获取任务的执行结果              String result = future.get();              System.out.println("Task result: " + result);          } catch (InterruptedException | ExecutionException e) {              e.printStackTrace();          }            // 关闭线程池          executorService.shutdown();      }  }
复制代码

2.2 提交 Runnable 任务

提交 Runnable 任务并获取 Future 对象,示例代码如下:


import java.util.concurrent.ExecutorService;  import java.util.concurrent.Executors;  import java.util.concurrent.Future;    public class SubmitRunnableDemo {      public static void main(String[] args) {          // 创建一个固定大小的线程池          ExecutorService executorService = Executors.newFixedThreadPool(2);            // 提交一个 Runnable 任务给线程池执行,并获取一个 Future 对象          Future<?> future = executorService.submit(new Runnable() {              @Override              public void run() {                  System.out.println("Task is running in thread: " + Thread.currentThread().getName());              }          });            // 检查任务是否完成(这里只是为了示例,实际使用中可能不需要这样做)          if (future.isDone()) {              System.out.println("Task is done");          } else {              System.out.println("Task is not done yet");          }            // 关闭线程池          executorService.shutdown();      }  }
复制代码

3.遇到未处理异常

线程池遇到未处理的异常执行行为和添加任务的方法有关,也就是说 execute 方法和 submit 方法在遇到未处理的异常时执行行为是不一样的

3.1 execute 方法遇到未处理异常

示例代码如下:


import java.util.concurrent.*;
public class ThreadPoolExecutorExceptionTest { public static void main(String[] args) { ThreadPoolExecutor executor = new ThreadPoolExecutor( 1, 1, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(100)); // 添加任务一 executor.execute(() -> { String tName = Thread.currentThread().getName(); System.out.println("线程名:" + tName); throw new RuntimeException("抛出异常"); }); // 添加任务二 executor.execute(() -> { String tName = Thread.currentThread().getName(); System.out.println("线程名:" + tName); throw new RuntimeException("抛出异常"); }); }}
复制代码


以上程序的执行结果如下:



从上述结果可以看出,线程池中的核心和最大线程数都为 1 的情况下,到遇到未处理的异常时,执行任务的线程却不一样,这说明了:当使用 execute 方法时,如果遇到未处理的异常,会抛出未捕获的异常,并将当前线程进行销毁

3.2 submit 方法遇到未处理异常

然而,当我们将线程池的添加任务方法换成 submit() 之后,执行结果又完全不同了,以下是示例代码:


import java.util.concurrent.*;
public class ThreadPoolExecutorExceptionTest { public static void main(String[] args) { ThreadPoolExecutor executor = new ThreadPoolExecutor( 1, 1, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(100)); // 添加任务一 Future<?> future = executor.submit(() -> { String tName = Thread.currentThread().getName(); System.out.println("线程名:" + tName); throw new RuntimeException("抛出异常"); }); // 添加任务二 Future<?> future2 =executor.submit(() -> { String tName = Thread.currentThread().getName(); System.out.println("线程名:" + tName); throw new RuntimeException("抛出异常"); }); try { future.get(); } catch (Exception e) { System.out.println("遇到异常:"+e.getMessage()); } try { future2.get(); } catch (Exception e) { System.out.println("遇到异常:"+e.getMessage()); } }}
复制代码


以上程序的执行结果如下:



从上述结果可以看出,submit 方法遇到未处理的异常时,并将该异常封装在 Future 的 get 方法中,而不会直接影响执行任务的线程,这样线程就可以继续复用了

小结

线程池在遇到未处理的异常时,不同添加任务的方法的执行行为是不同的:


  • execute 方法:遇到未处理的异常,线程会崩溃,并打印异常信息。

  • submit 方法:遇到未处理的异常,线程本身不会受到影响(线程可以复用),只是将异常信息封装到返回的对象 Future 中。

课后思考

为什么遇到未处理的异常时,execute 方法中的线程会崩溃,而 submit 方法中的线程却可以复用?


本文已收录到我的面试小站 www.javacn.site,其中包含的内容有:Redis、JVM、并发、并发、MySQL、Spring、Spring MVC、Spring Boot、Spring Cloud、MyBatis、设计模式、消息队列等模块。

用户头像

王磊

关注

javacn.site 2018-08-25 加入

我的小站:javacn.site

评论

发布
暂无评论
面试官:线程池遇到未处理的异常会崩溃吗?_王磊_InfoQ写作社区