ThreadPoolTaskExecutor配置问题
最近线上出现一个奇葩问题,使用的是ThreadPoolTaskExecutor来处理后续服务调用,刚开始运行ThreadPoolTaskExecutor处理后续服务调用是没有问题的,但是一段时间之后,发现后续服务一直没有被调用,导致了极其严重的后果
有关spring中ThreadPoolTaskExecutor具体如下:
<bean id="threadPoolTaskExecutor"
class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<!-- 核心线程数,默认为1 -->
<property name="corePoolSize" value="5" />
<!-- 最大线程数,默认为Integer.MAX_VALUE -->
<property name="maxPoolSize" value="16" />
<!-- 队列最大长度,一般需要设置值>=notifyScheduledMainExecutor.maxNum;默认为Integer.MAX_VALUE -->
<!--<property name="queueCapacity" value="10" />-->
<!-- 线程池维护线程所允许的空闲时间,默认为60s -->
<property name="keepAliveSeconds" value="300" />
<!-- 线程池对拒绝任务(无线程可用)的处理策略,
目前只支持AbortPolicy、CallerRunsPolicy;默认为后者
-->
<property name="rejectedExecutionHandler">
<!-- AbortPolicy:直接抛出java.util.concurrent.RejectedExecutionException异常 -->
<!-- CallerRunsPolicy:
主线程直接执行该任务,执行完之后尝试添加下一个任务到线程池中,
-->
<!-- DiscardOldestPolicy:
抛弃旧的任务、暂不支持;会导致被丢弃的任务无法再次被执行
-->
<!-- DiscardPolicy:
抛弃当前任务、暂不支持;会导致被丢弃的任务无法再次被执行
-->
<bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" />
</property>
</bean>
那就不得不了解一下java.util.concurrent
包下Executor构架了
回忆一下线程池工作原理:
- 如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(需要获得全局锁)
- 如果运行的线程等于或多于corePoolSize ,则将任务加入BlockingQueue
- 如果无法将任务加入BlockingQueue(队列已满),则创建新的线程来处理任务(需要获得全局锁)
- 如果创建新线程将使当前运行的线程超出maxiumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()方法
测试场景1
首先,注释queueCapacity
的一行
任务:
public class CustomRunnable implements Runnable {
private int id;
public CustomRunnable(int id) {
this.id = id;
}
@Override
public void run() {
try {
System.out.println("begin execute "+ Thread.currentThread().getName()
+ "-- task id: "+ id);
String rs = ClientUtil.get("http://www.****.com");
System.out.println("end execute task: "+ id);
} catch (Exception e) {
e.printStackTrace();
}
}
}
测试案例:
@Test
public void threadTest() throws InterruptedException {
for (int i=0; i< 35; i++){
Thread t= new Thread(new CustomRunnable(i));
executor.execute(t);
}
Thread.sleep(1800000);
}
测试结果:
七月 09, 2018 5:46:47 下午 org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor initialize
信息: Initializing ExecutorService 'threadPoolTaskExecutor'
begin execute threadPoolTaskExecutor-1-- task id: 0
begin execute threadPoolTaskExecutor-2-- task id: 1
begin execute threadPoolTaskExecutor-3-- task id: 2
begin execute threadPoolTaskExecutor-4-- task id: 3
begin execute threadPoolTaskExecutor-5-- task id: 4
end execute task: 4
begin execute threadPoolTaskExecutor-5-- task id: 5
end execute task: 1
begin execute threadPoolTaskExecutor-2-- task id: 6
end execute task: 0
begin execute threadPoolTaskExecutor-1-- task id: 7
end execute task: 2
begin execute threadPoolTaskExecutor-3-- task id: 8
end execute task: 3
begin execute threadPoolTaskExecutor-4-- task id: 9
...
可以发现,一开始线程池就创建了corePoolSize
大小的线程,对于之后的新加进的任务,就放到BlockingQueue中,默认是使用LinkedBlockingQueue
,大小是Integer.MAX_VALUE,因为队列大小太大,所以就不会创建maxPoolSize
大小的线程数量,因此,只有线程处理完当前任务,才会去处理下一个任务,所以,刚加进去的任务得不到立即处理
测试场景2
只需要打开queueCapacity
的一行,其他不变
测试结果:
七月 09, 2018 6:07:13 下午 org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor initialize
信息: Initializing ExecutorService 'threadPoolTaskExecutor'
begin execute threadPoolTaskExecutor-1-- task id: 0
begin execute threadPoolTaskExecutor-2-- task id: 1
begin execute threadPoolTaskExecutor-3-- task id: 2
begin execute threadPoolTaskExecutor-4-- task id: 3
begin execute threadPoolTaskExecutor-5-- task id: 4
begin execute threadPoolTaskExecutor-6-- task id: 15
begin execute threadPoolTaskExecutor-7-- task id: 16
begin execute threadPoolTaskExecutor-8-- task id: 17
begin execute threadPoolTaskExecutor-9-- task id: 18
begin execute threadPoolTaskExecutor-10-- task id: 19
begin execute threadPoolTaskExecutor-11-- task id: 20
begin execute threadPoolTaskExecutor-12-- task id: 21
begin execute threadPoolTaskExecutor-14-- task id: 23
begin execute threadPoolTaskExecutor-15-- task id: 24
begin execute main-- task id: 26
begin execute threadPoolTaskExecutor-13-- task id: 22
begin execute threadPoolTaskExecutor-16-- task id: 25
begin execute threadPoolTaskExecutor-11-- task id: 5
end execute task: 15
begin execute threadPoolTaskExecutor-6-- task id: 6
end execute task: 23
begin execute threadPoolTaskExecutor-14-- task id: 7
end execute task: 4
begin execute threadPoolTaskExecutor-5-- task id: 8
end execute task: 17
begin execute threadPoolTaskExecutor-8-- task id: 9
....
可以发现,因为初始任务数量大于corePoolSize
大小,所以线程池初始化就创建了maxPoolSize
大小数量的纯种,对于后续新加进的任务会入到BlockingQueue队列中去,之后等待线程处理完一个任务之后再处理队列中的任务
猜想
线上出现这种原因可能就是因为queueCapacity
被设置成了默认(Integer.MAX_VALUE),而且初始化纯种的corePoolSize
数量过少,并且线程处理速度较慢(业务逻辑,网络请求等等原因),导致后续任务会一直填加到队列中去,迟迟得不到立即处理。
解决方案
手动设置queueCapacity
大小,网络请求原因的话,可以设置超时时间;业务逻辑的话,另辟蹊径。。。