Java 面试 多线程遇到的问题,如何处理

问题描述

某服务在运行过程中抛出了 RejectedExecutionException。

配置参数: corepoolsize = 50, maxpoolsize =50, workqueue 为 SynchronousQueue

现象: 当新的task被拒绝时,pool size未达到配置值50.

复制代码
Caused by: java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@3c359b91 rejected from java.util.concurrent.ThreadPoolExecutor@755319d6[Running, pool size = 42, active threads = 27, queued tasks = 0, completed tasks = 29]
	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)

分析

日志中的打印是 ThreadPoolExecutor的 toString()方法的输出

  • Running 对应 ctl 的状态位,
  • pool size对应 workers.size(),
  • active threads对应处于locked的线程数(执行状态)
  • queued tasks对应 workQueue.size()
  • completed tasks对应完成的task数
  • RejectedExecutionException 说明在 addWorker()函数中获取到的 ctl中的worker数量已经达到了maxpoolsize 50.

疑问

1、ctl中记录的 worker 数量和 worker.size () 在什么情况下会不一致?

这个主要由于toString()⽅法输出的是works的⼤⼩,⽽加任务时是判断的ctl中的线程数⽬,ctl中的线程数⽬并不⼀定等于真实的线程数⽬,如下为代码中的注释:

The workerCount is the number of workers that have been permitted to start and not permitted to stop. The value may be transiently different from the actual number of live threads, for example when a ThreadFactory fails to create a thread when asked, and when exiting threads are still performing bookkeeping before terminating. The user-visible pool size is reported as the current size of the workers set. 即ctl中的workerCount是被允许执⾏且不允许停⽌的线程数⽬,这个值可能会短暂性的与真实活跃的线程数量不同。

2、pool size和 active threads 为什么不一致?

分析出以上结论后,我们只需要创建多个线程往线程池丢任务即可,然后让⼤多数线程将ctl中的workCount加1,并让线程停到将worker加⼊到workers之前停⽌,这样⽤户看到的⽇志就会出现,虽然workers的⼤⼩很⼩,就出现了拒绝加⼊的现象。 demo:

java 复制代码
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Test {
    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10, 1,
                TimeUnit.MINUTES, new ArrayBlockingQueue<>(2));
        for (int i = 0; i < 30; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    threadPoolExecutor.execute(new Runnable() {
                        @Override
                        public void run() {
                            System.out.println("I was executed!");
                        }
                    });
                }
            }).start();
        }
    }
}

这⾥将线程0-1置为active状态,将2-9停到增加完ctl值后,即break retry; 语句,然后10-11会加⼊到队列中,当加⼊线程12启动时,就会出现拒绝加⼊的异常,如下图:

如果想让队列中元素为0,则只需让被拒绝线程在打印错误信息之前,让活跃线程将队列中的任务执⾏完即可,如下为结果中的⽇志:

同时,由于worker在获取执⾏任务前,会短暂的释放掉锁(⽤来判断worker是否为活跃状态的锁),所以也可以让active thread⼩于pool thread,如下:

⾄此,⽇志中的所有现象都可以被解释了。故为正常现象

相关推荐
顺凡6 小时前
删一个却少俩:Antd Tag 多节点同时消失的原因
前端·javascript·面试
音符犹如代码8 小时前
Java并发List实战:CopyOnWriteArrayList原理与ArrayList常见面试题
java·开发语言·面试·list
蚂小蚁13 小时前
一文吃透:宏任务、微任务、事件循环、浏览器渲染、Vue 批处理与 Node 差异(含性能优化)
前端·面试·架构
uhakadotcom14 小时前
在使用cloudflare workers时,假如有几十个请求,如何去控制并发?
前端·面试·架构
嵌入式学习之旅17 小时前
嵌入式面试1103
面试·职场和发展
蒙奇D索大17 小时前
【算法】递归算法实战:汉诺塔问题详解与代码实现
c语言·考研·算法·面试·改行学it
重铸码农荣光19 小时前
从「[1,2,3].map (parseInt)」踩坑,吃透 JS 数组 map 与包装类核心逻辑
面试·node.js
疯狂踩坑人19 小时前
结合400行mini-react代码,图文解说React原理
前端·react.js·面试
程序员爱钓鱼21 小时前
Python编程实战——Python实用工具与库:Numpy基础
后端·python·面试
晨非辰1 天前
【数据结构初阶】--从排序算法原理分析到代码实现操作,参透插入排序的奥秘!
c语言·开发语言·数据结构·c++·算法·面试·排序算法