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

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

首先,这个问题考察的是你对线程池 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、设计模式、消息队列等模块。

相关文章
|
2月前
线程CPU异常定位分析
【10月更文挑战第3天】 开发过程中会出现一些CPU异常升高的问题,想要定位到具体的位置就需要一系列的分析,记录一些分析手段。
67 0
|
14天前
|
存储 缓存 算法
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
本文介绍了多线程环境下的几个关键概念,包括时间片、超线程、上下文切换及其影响因素,以及线程调度的两种方式——抢占式调度和协同式调度。文章还讨论了减少上下文切换次数以提高多线程程序效率的方法,如无锁并发编程、使用CAS算法等,并提出了合理的线程数量配置策略,以平衡CPU利用率和线程切换开销。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
|
2月前
|
监控 Java
在实际应用中选择线程异常捕获方法的考量
【10月更文挑战第15天】选择最适合的线程异常捕获方法需要综合考虑多种因素。没有一种方法是绝对最优的,需要根据具体情况进行权衡和选择。在实际应用中,还需要不断地实践和总结经验,以提高异常处理的效果和程序的稳定性。
22 3
|
2月前
|
监控 Java
捕获线程执行异常的多种方法
【10月更文挑战第15天】捕获线程执行异常的方法多种多样,每种方法都有其特点和适用场景。在实际开发中,需要根据具体情况选择合适的方法或结合多种方法来实现全面有效的线程异常捕获。这有助于提高程序的健壮性和稳定性,减少因线程异常带来的潜在风险。
23 1
|
2月前
|
监控 API
Hook 线程与捕获线程执行异常
【10月更文挑战第11天】Hook 线程和捕获线程执行异常是多线程编程中不可或缺的技术。通过深入理解和掌握这些方法,我们可以提高程序的稳定性和可靠性,更好地应对各种异常情况。同时,在实际应用中要注意平衡性能和准确性,制定合理的异常处理策略,以确保程序的正常运行。
30 1
|
3月前
|
存储 缓存 安全
【Java面试题汇总】多线程、JUC、锁篇(2023版)
线程和进程的区别、CAS的ABA问题、AQS、哪些地方使用了CAS、怎么保证线程安全、线程同步方式、synchronized的用法及原理、Lock、volatile、线程的六个状态、ThreadLocal、线程通信方式、创建方式、两种创建线程池的方法、线程池设置合适的线程数、线程安全的集合?ConcurrentHashMap、JUC
【Java面试题汇总】多线程、JUC、锁篇(2023版)
|
3月前
|
安全 Java API
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
String常量池、String、StringBuffer、Stringbuilder有什么区别、List与Set的区别、ArrayList和LinkedList的区别、HashMap底层原理、ConcurrentHashMap、HashMap和Hashtable的区别、泛型擦除、ABA问题、IO多路复用、BIO、NIO、O、异常处理机制、反射
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
|
3月前
|
消息中间件 存储 前端开发
面试官:说说停止线程池的执行流程?
面试官:说说停止线程池的执行流程?
52 2
面试官:说说停止线程池的执行流程?
|
3月前
|
消息中间件 前端开发 NoSQL
面试官:如何实现线程池任务编排?
面试官:如何实现线程池任务编排?
36 1
面试官:如何实现线程池任务编排?
|
2月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
48 1
C++ 多线程之初识多线程

相关实验场景

更多