今天是很蛋疼的一天,排查一个bug排查了4个多小时。
情形简化之后大概是这样的:
我使用了spring的ThreadPoolTaskExecutor来进行并发时候的异步处理。并且给任务Runnable加上了CyclicBarrier,以达到让所有线程处理完之后再进行主线程的下一步操作的目的。其中executor的配置如下:
<bean id="coreBlockExecutor"
class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="5" />
<property name="maxPoolSize" value="2000" />
<property name="queueCapacity" value="1000" />
</bean>
结果,bug就这样华丽丽的出现了:我new出来了1000个runnable,但无论如何也只能执行出5个runnable的结果(即corePoolSize),其他runnable就一直呆在blockingqueue里面不动弹了。
**************************************************** 华丽的分割线——原因 ****************************************************
花了n久去查看ThreadPoolTaskExecutor和ThreadPoolExecutor的源码。
发现是这样的,其实ThreadPoolTaskExecutor也就是调用了ThreadPoolExecutor,在后者中,有这样一个方法:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
if (runState == RUNNING && workQueue.offer(command)) {
if (runState != RUNNING || poolSize == 0)
ensureQueuedTaskHandled(command);
}
else if (!addIfUnderMaximumPoolSize(command))
reject(command); // is shutdown or saturated
}
}
可以看到,在当前线程池的线程数大于等于corePoolSize时候,会判断
if (runState == RUNNING && workQueue.offer(command))
也就是,当前线程池是处于运行状态并且队列是否还能插入runnable。
所以,当队列满的时候,也就是会去执行下面的代码
else if (!addIfUnderMaximumPoolSize(command))
reject(command); // is shutdown or saturated
即当前线程数如果小于最大线程数,则会调用addThread方法,将runnbale实例化成一个内部类Worker,加入线程池中运行。代码如下:
private Thread addThread(Runnable firstTask) {
Worker w = new Worker(firstTask);
Thread t = threadFactory.newThread(w);
if (t != null) {
w.thread = t;
workers.add(w);
int nt = ++poolSize;
if (nt > largestPoolSize)
largestPoolSize = nt;
}
return t;
}
而普通情况,即使填入executor的runnable数量不能填满queue,但在核心线程中运行的任务Worker结束之后,过了最大空闲时间(keepAliveTime)之后,即会释放线程,去从queue中获取等待中的任务。
代码如下:
public void run() {
try {
Runnable task = firstTask;
firstTask = null;
while (task != null || (task = getTask()) != null) {
runTask(task);
task = null;
}
} finally {
workerDone(this);
}
}
}
Runnable getTask() {
for (;;) {
try {
int state = runState;
if (state > SHUTDOWN)
return null;
Runnable r;
if (state == SHUTDOWN) // Help drain queue
r = workQueue.poll();
else if (poolSize > corePoolSize || allowCoreThreadTimeOut)
r = workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS);
else
r = workQueue.take();
if (r != null)
return r;
if (workerCanExit()) {
if (runState >= SHUTDOWN) // Wake up others
interruptIdleWorkers();
return null;
}
// Else retry
} catch (InterruptedException ie) {
// On interruption, re-check runState
}
}
}
原来Worker中的run方法是通过轮询来获取当前任务是否结束以及从队列里面继续拿任务的。当符合以下其中一个条件时:
可以看到,只有Worker当前的任务已完成的时候,才能去队列里面拿其他任务。否则,就只能keep on waiting了
所以原因就出来了:使用了CyclicBarrier进行栅栏式的改装之后,所有核心线程中的任务都会一直等待,不会空闲下来。这样核心线程就永远处于尴尬的被hold住的状态了。既不能结束当前的任务,也无法从队列获取新的任务,更无法中止线程了。
所以,像我这样没有仔细看源码的码农,碰到问题就悲剧了。哎
**************************************************** 华丽的分割线——解决方法 ****************************************************
1、允许的情况下,barrier的await设置过期时间
2、仔细考虑queue长度和并发规模
总之,好像也没什么特别好的办法。
分享到:
相关推荐
ThreadPoolExecutor使用和思考
ThreadPoolExecutor的使用和Android常见的4种线程池使用介绍
* 降低了系统的开销:ThreadPoolExecutor 可以重复使用线程,避免了频繁创建和销毁线程的开销。 * 提高了系统的灵活性:ThreadPoolExecutor 可以根据不同的工作负载动态地调整线程池的大小。 ThreadPoolExecutor 的...
NULL 博文链接:https://bijian1013.iteye.com/blog/2284676
JDK1[1].5中的线程池(ThreadPoolExecutor)使用简介
了解ThreadPoolExecutor的这些核心概念和工作流程,能帮助开发者更好地调整线程池参数,优化并发性能,以及在系统出现问题时进行排查和定位。在实际开发中,合理配置线程池参数,避免线程池过载,是保障系统稳定运行...
"JDK1.5中的线程池(java.util.concurrent.ThreadPoolExecutor)使用" JDK1.5中的线程池(java.util.concurrent.ThreadPoolExecutor)使用是Java多线程编程中的一种重要概念。随着多线程编程的普及,线程池的使用变得...
1. 降低资源消耗:通过复用已存在的线程,避免频繁创建和销毁线程带来的开销。 2. 提高响应速度:任务无需等待新线程的创建即可执行,提高了处理速度。 3. 提升线程的可管理性:通过统一的线程池,可以更好地分配、...
在《阿里巴巴java开发手册》中...另外由于前面几种方法内部也是通过ThreadPoolExecutor方式实现,使用ThreadPoolExecutor有助于大家明确线程池的运行规则,创建符合自己的业务场景需要的线程池,避免资源耗尽的风险。
ThreadPoolExecutor源码解析.md
标题"redis list queue 和 ThreadPoolExecutor 结合"指的是如何将Redis的List数据结构作为消息队列与Java的ThreadPoolExecutor结合使用,以实现任务的异步处理和负载均衡。以下是对这一主题的详细探讨: 1. **Redis...
本文将详细讲解如何使用Java中的`ThreadPoolExecutor`来抓取论坛帖子列表,结合源码分析和实用工具的应用。 首先,我们要了解线程池的基本原理。线程池是由一组预先创建的线程组成的,这些线程可以复用,而不是每次...
1.资源简介:PyQt5中使用多线程模块QThread解决了PyQt5界面程序执行比较耗时操作时,程序卡顿出现的无响应以及界面输出无法实时显示的问题,采用线程池ThreadPoolExecutor解决了ping多个IP多任务耗时问题。...
使用ThreadPoolExecutor可以带来以下几个优点: * 提高系统的并发性:ThreadPoolExecutor可以动态地调整池中的线程数,以便更好地处理任务。 * 提高系统的可扩展性:ThreadPoolExecutor可以根据实际情况来调整池中...
* 核心线程数和最大线程数的设置:核心线程数和最大线程数需要根据实际情况进行设置,过小可能导致线程池无法处理任务,过大可能导致系统资源浪费。 * 任务队列的选择:选择合适的任务队列可以提高...
主要介绍了java ThreadPoolExecutor使用方法简单介绍的相关资料,需要的朋友可以参考下
(转)线程池:java_util_ThreadPoolExecutor 比较详细的介绍了ThreadPoolExecutor用法与属性
在Java的多线程编程中,Spring框架提供了一种便捷的方式来管理和配置线程池,这就是`ThreadPoolTaskExecutor`。...在实际开发中,根据具体需求灵活配置和使用这些工具,能够提升程序的并发处理能力和响应速度。
线程池ThreadPoolExecutor实战及其原理分析(下)线程池ThreadPoolExecutor实战及其原理分析(下)线程池ThreadPoolExecutor实战及其原理分析(下)线程池ThreadPoolExecutor实战及其原理分析(下)线程池ThreadPoolExecutor...