最近在搞线程池,用到了CallerRunsPolicy这个拒绝策略。
因为考虑到数据一条都不能丢失,所以选择了CallerRunsPolicy策略。保证数据来了不会丢。天真的幻想着这策略好啊!
经过了一段时间的实战考验,发现了一个问题,数据处理不过来,经常堆积,这样还行能接受。
在从单线程改用多线程后,阻塞队列在高峰期时经常是满的。
阻塞队列经常是满了,于是考虑从处理效率上入手,(同事解决的)优化了程序处理速度和数据库写入效率,缓解了这个问题,线程池还是用的最原始的版本,网上一搜一大把那种。这个版本上线运行了,简称 V-1.0。
上面的问题解决了之后,新的问题就出现了。
因为线上运行了一段时间,运行几天之后就需要重启一下,那么就需要再进行第二波的优化。
第二波优化了线程池,数据接收后再用队列做了分流,堪称更稳定的版本,数据几乎没有延迟。
于是经过改造,使用了新的方法去应用,运行了几天并观察对比和线上的数据。
于是问题就来了。
有些数据比较多的,线上和测试环境的数据,有些大部分对得上,有些大部分对不上,这就很奇怪了。
经过对比排查:
因为线上运行的线程池是旧版本V-1.0,拒绝策略选择了CallerRunsPolicy。因为有些处理逻辑是需要数据时间顺序进行处理的,在高峰期的时候,大量的数据阻塞在阻塞队列里面等待处理:
当线程数线程池的最大线程数并且阻塞队列已满的情况下,后到的数据会执行拒绝策略,让调用线程(提交任务的线程)直接执行此任务,导致数据处理顺序不一致。
举个例:
线程数满并且阻塞队列满,阻塞队列中存在线程等待,
数据的顺序为 【11,12,13,14,15,16,17,18】
前面线程正在处理,后面有新数据【19】,【20】到来,发现队列满了,线程数也到最大了,
那就让调用线程执行这个任务,所以就会出现以下的处理顺序:
(11,12,19,13,14……)等等类似的情况。
再写个代码来说明一下吧
ThreadPoolExecutor executorA = new ThreadPoolExecutor(4,4, 10L, TimeUnit.MILLISECONDS,
new LinkedBlockingDeque<>(100),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy()
);
for (int i = 0; i < 500; i++) {
int s = i;
executorA.execute(()->{
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("【" + LocalTime.now() + "】线程 " + Thread.currentThread() + "正在执行任务" + s);
});
}
部分运行结果
【09:32:15.570】线程 Thread[pool-1-thread-1,5,main]正在执行任务0
【09:32:15.570】线程 Thread[pool-1-thread-3,5,main]正在执行任务2
【09:32:15.570】线程 Thread[pool-1-thread-4,5,main]正在执行任务3
【09:32:15.570】线程 Thread[main,5,main]正在执行任务104
【09:32:15.570】线程 Thread[pool-1-thread-2,5,main]正在执行任务1
【09:32:15.771】线程 Thread[pool-1-thread-1,5,main]正在执行任务4
【09:32:15.772】线程 Thread[pool-1-thread-2,5,main]正在执行任务7
【09:32:15.772】线程 Thread[main,5,main]正在执行任务108
【09:32:15.772】线程 Thread[pool-1-thread-4,5,main]正在执行任务6
【09:32:15.773】线程 Thread[pool-1-thread-3,5,main]正在执行任务5
【09:32:15.972】线程 Thread[pool-1-thread-1,5,main]正在执行任务8
【09:32:15.973】线程 Thread[main,5,main]正在执行任务112
【09:32:15.973】线程 Thread[pool-1-thread-2,5,main]正在执行任务9
【09:32:15.973】线程 Thread[pool-1-thread-4,5,main]正在执行任务10
【09:32:15.974】线程 Thread[pool-1-thread-3,5,main]正在执行任务11
【09:32:16.173】线程 Thread[pool-1-thread-1,5,main]正在执行任务12
【09:32:16.174】线程 Thread[pool-1-thread-2,5,main]正在执行任务13
【09:32:16.174】线程 Thread[pool-1-thread-4,5,main]正在执行任务14
【09:32:16.174】线程 Thread[main,5,main]正在执行任务117
【09:32:16.175】线程 Thread[pool-1-thread-3,5,main]正在执行任务15
【09:32:16.374】线程 Thread[pool-1-thread-1,5,main]正在执行任务16
【09:32:16.375】线程 Thread[pool-1-thread-2,5,main]正在执行任务17
【09:32:16.375】线程 Thread[main,5,main]正在执行任务123
【09:32:16.375】线程 Thread[pool-1-thread-4,5,main]正在执行任务18
【09:32:16.376】线程 Thread[pool-1-thread-3,5,main]正在执行任务19
【09:32:16.575】线程 Thread[pool-1-thread-1,5,main]正在执行任务20
【09:32:16.576】线程 Thread[pool-1-thread-4,5,main]正在执行任务22
【09:32:16.576】线程 Thread[main,5,main]正在执行任务127
【09:32:16.576】线程 Thread[pool-1-thread-2,5,main]正在执行任务21
【09:32:16.577】线程 Thread[pool-1-thread-3,5,main]正在执行任务23
【09:32:16.776】线程 Thread[pool-1-thread-1,5,main]正在执行任务24
【09:32:16.777】线程 Thread[main,5,main]正在执行任务132
【09:32:16.777】线程 Thread[pool-1-thread-4,5,main]正在执行任务25
【09:32:16.777】线程 Thread[pool-1-thread-2,5,main]正在执行任务26
【09:32:16.778】线程 Thread[pool-1-thread-3,5,main]正在执行任务27
【09:32:16.977】线程 Thread[pool-1-thread-1,5,main]正在执行任务28
【09:32:16.978】线程 Thread[pool-1-thread-2,5,main]正在执行任务30
【09:32:16.978】线程 Thread[main,5,main]正在执行任务137
结果很明显了。
当在多线程中数据处理时需要强关联数据时间顺序时,最好考虑一下其他的处理方式,避免踩坑。