无论是使用jdk的线程池ThreadPoolExecutor 还是spring的线程池ThreadPoolTaskExecutor 都会使用到一个阻塞队列来进行存储线程任务。
当线程不够用时,则将后续的任务暂存到 阻塞队列中,等待有空闲线程来进行。
当这个阻塞队列满了的时候,会出现两种情况
正在运行的线程数量小于 maximumPoolSize,那么还是要创建线程运行这个任务;
正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会通过一个策略进行对后续的任务进行处理。
四种策略
ThreadPoolExecutor.AbortPolicy() 抛出java.util.concurrent.RejectedExecutionException异常
ThreadPoolExecutor.CallerRunsPolicy() 重试添加当前的任务,他会自动重复调用execute()方法
ThreadPoolExecutor.DiscardOldestPolicy() 抛弃旧的任务
ThreadPoolExecutor.DiscardPolicy() 抛弃当前的任务
其他很容易看出来,最近踩了一个ThreadPoolExecutor.CallerRunsPolicy()的坑。
情景是这样的:我需要通过线程进行创建长连接,当开启了15个线程的是,线程池最大即达到了maximumPoolSize的数量的时候,继续增大请求量,会无缘无故的多出来
一个长连接,由于有负载均衡,导致了很多请求无法从长连接中得到。
经过很长时间的测试,最终发现了是ThreadPoolExecutor.CallerRunsPolicy()策略导致的。
这个测试是当你的线程数达到最大,阻塞队列也满了的时候,之后的任务会强制先执行,但是没有了线程谁来执行呢,这个策略会强制中断主线程进行执行这个任务。
即是说,当我的量上来,线程池不够用的时候,中断了我的主线程,主线程没有长连接,就建立了一个新的长连接,那边进行了负载均衡,但是这边不会在进行主线程了,
导致很多请求接收不到。
假设队列大小为 10,corePoolSize 为 3,maximumPoolSize 为 6,那么当加入 20 个任务时,执行的顺序就是这样的:首先执行任务 1、2、3,然后任务 4~13 被放入队列。这时候队列满了,任务 14、15、16 会被马上执行,最终顺序是:1、2、3、14、15、16、4、5、6、7、8、9、10、11、12、13。
踩坑完毕
最终做法:
换成ThreadPoolExecutor.DiscardPolicy()策略,还是从已经建立的连接中进行 的到请求,不要继续创建连接。