「视频版」当线程池溢出之后,程序会奔溃吗?面试突击 007 期

简介: 「视频版」当线程池溢出之后,程序会奔溃吗?面试突击 007 期

哈喽,大家好,我是老王,欢迎来到第 7 期的 Java 面试突击。


本文的面试题是,当线程池的任务溢出之后,程序会奔溃吗?


这个问题问的是关于线程池的任务数超过线程池的承载能力之后,会出现什么情况?

那么,我们本文就手撸模拟一个线程池溢出的情况,来看程序的执行情况。


涉及知识点


  1. 核心线程数和最大线程数有什么区别?
  2. 如何模拟线程池溢出?
  3. 拒绝策略的执行流程是什么?
  4. 什么是线程池的拒绝策略?
  5. Java 自带的拒绝策略有哪些?
  6. 如何自定义拒绝策略?


视频面试答案


视频内容(因为视频比较大,分成了两个视频来展示 ):

QQ图片20220117194850.png点击查看原视频链接

QQ图片20220117194853.png点击查看原视频链接


图文面试答案


当线程池的任务溢出之后,程序并不会奔溃,这时候会触发线程池的拒绝策略,Java 自带的拒绝策略有四种:


  1. AbortPolicy:终止策略,线程池终止执行,并直接抛出异常,Java 默认此拒绝策略;
  2. CallerRunsPolicy:把任务交给当前线程执行(本来是线程池自己要执行的,结果处理不过来就交给当前的主线程去处理);
  3. DiscardPolicy:忽略此任务(最新的任务);
  4. DiscardOldestPolicy:忽略最早的任务(最久的任务)。


拒绝策略的执行流程比较绕,这是因为线程池有三个重要的参数:


核心线程数(corePoolSize)、最大线程数(maximumPoolSize)、线程池的任务队列(BlockingQueue),大部分搞不清楚核心线程数和最大线程数有什么区别?


核心线程数是指在正常情况下线程池内的线程数量;而最大线程数指的是当线程池的任务队列存储超过最大值之后,可以创建最多的线程数量


当任务比较少的时候,线程数量会根据设置的超时时间,回归线程的数量为核心线程数量,这个时候最大线程数就暂时没用了(没有发挥的余地了)。



拒绝策略的执行流程是:当提交的任务数量大于核心线程数时,任务会被放入到线程池的任务队列中,当任务超过了最大队列值时,判断当前线程数量是否大于最大线程数,如果小于最大线程数则会新创建线程处理次任务,相反的情况下就会执行拒绝策略,如下图所示:


微信图片_20220117195034.png


模拟线程池溢出


public static void main(String[] args) {
     ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 4, 10, TimeUnit.SECONDS,
           new LinkedBlockingQueue<>(1),
           new ThreadPoolExecutor.AbortPolicy());
     for (int i = 0; i < 6; i++) {
         executor.execute(() -> {
             System.out.println(Thread.currentThread().getName());
         });
     }
 }


程序的执行结果如下:


pool-1-thread-2

pool-1-thread-2

pool-1-thread-1

pool-1-thread-3

pool-1-thread-4

Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task org.example.App$$Lambda$1/1096979270@7cca494b rejected from java.util.concurrent.ThreadPoolExecutor@7ba4f24f[Running, pool size = 4, active threads = 4, queued tasks = 1, completed tasks = 0]

at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)

at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)

at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)

at org.example.App.main(App.java:13)


从执行结果可以看出,循环在执行第 6 次就抛出异常了,这是因为最大线程数为 4,而队列最大只能存储 1 个任务,所以在第 6 个任务过来的时候,线程池已经超负荷运行了,只能执行拒绝策略了,而我们设置的拒绝策略是 AbortPolicy 所以会抛出异常。


自定义拒绝策略


除了 Java 自带的四种拒绝策略外,我们还可以自定义拒绝策略,代码如下:


public static void main(String[] args) throws InterruptedException {
    ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 4, 10, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(1), new RejectedExecutionHandler() {
        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            // 添加业务处理代码
            System.out.println("自定义拒绝策略");
        }
    });
    for (int i = 0; i < 7; i++) {
        executor.execute(() -> {
            System.out.println(Thread.currentThread().getName());
        });
    }
}


以上程序执行结果如下:


自定义拒绝策略

自定义拒绝策略

pool-1-thread-1

pool-1-thread-1

pool-1-thread-2

pool-1-thread-3

pool-1-thread-4


可以看出自定义拒绝策略,只需要重写 RejectedExecutionHandler 接口的 rejectedExecution 方法即可,可以在此方法中添加自己的业务处理代码。


小结


本文讲了线程池任务新增时的执行流程,先判断是否有空闲线程,如果的话直接执行任务,如果没有的话再判断任务队列是否是否饱和,如果不饱和把任务存储到队列中,如果饱和需要判断当前线程数是否大于最大线程数,如果小于则新增线程执行此任务,反之则执行拒绝策略。Java 提供了四种拒绝策略,你可以通过重写 RejectedExecutionHandler 接口来自定义拒绝策略。


更多执行细节和更多知识点说明,详见本文的视频部分。

相关文章
|
2月前
|
Java 开发者
如何通过易语言多线程提升程序响应速度
如何通过易语言多线程提升程序响应速度
183 62
|
22天前
|
安全 Java
线程安全的艺术:确保并发程序的正确性
在多线程环境中,确保线程安全是编程中的一个核心挑战。线程安全问题可能导致数据不一致、程序崩溃甚至安全漏洞。本文将分享如何确保线程安全,探讨不同的技术策略和最佳实践。
33 6
|
2月前
|
Java 开发者
如何通过易语言多线程提升程序响应速度?
如何通过易语言多线程提升程序响应速度?
|
3月前
|
存储 缓存 安全
【Java面试题汇总】多线程、JUC、锁篇(2023版)
线程和进程的区别、CAS的ABA问题、AQS、哪些地方使用了CAS、怎么保证线程安全、线程同步方式、synchronized的用法及原理、Lock、volatile、线程的六个状态、ThreadLocal、线程通信方式、创建方式、两种创建线程池的方法、线程池设置合适的线程数、线程安全的集合?ConcurrentHashMap、JUC
【Java面试题汇总】多线程、JUC、锁篇(2023版)
|
3月前
|
消息中间件 前端开发 NoSQL
面试官:线程池遇到未处理的异常会崩溃吗?
面试官:线程池遇到未处理的异常会崩溃吗?
82 3
面试官:线程池遇到未处理的异常会崩溃吗?
|
3月前
|
消息中间件 存储 前端开发
面试官:说说停止线程池的执行流程?
面试官:说说停止线程池的执行流程?
56 2
面试官:说说停止线程池的执行流程?
|
3月前
|
消息中间件 前端开发 NoSQL
面试官:如何实现线程池任务编排?
面试官:如何实现线程池任务编排?
43 1
面试官:如何实现线程池任务编排?
|
2月前
|
监控 Java API
|
4月前
|
Java
【多线程面试题二十五】、说说你对AQS的理解
这篇文章阐述了对Java中的AbstractQueuedSynchronizer(AQS)的理解,AQS是一个用于构建锁和其他同步组件的框架,它通过维护同步状态和FIFO等待队列,以及线程的阻塞与唤醒机制,来实现同步器的高效管理,并且可以通过实现特定的方法来自定义同步组件的行为。
【多线程面试题二十五】、说说你对AQS的理解