1.场景
2.方案
1.直接按照逻辑
这样虽然简单,但是问题也很明显,就是都是串行,效率比较低!
2.利用并行优化对账系统:
while循环里面每次都会创建新的线程,而创建线程可是个耗时的操作。所以最好是创建出来的线程能够循环利用,估计这时你已经想到线程池了,是的,线程池就能解决这个问题!但是线程池的方案里,线程根本就不会退出,所以join就失效了!
方案3 用CountDownLatch+线程池:执行图还是如上图
方案4:CyclicBarrier实现线程同步进一步优化性能:
优化思路:我们都知道前面俩个方案都是先执行查询单和派送单,然后再进行对账!也就是说,执行查询单和派送单俩个操作是并行,但是和对账操作是串行(对账操作等到俩个操作都执行完成,再统一dui账),这个时候我们优化的思路就是可不可以不等执行查询单和派送单都执行完成再对账,可以等查询单和派送单分别查询出有记录的时候,就执行对账操作!
如何实现优化:其实这有点类似于生产者消费者模型,就是需要俩个队列用来存放未对账单和派送单每次查询的记录,然后消费者也就是对账操作这个线程,负责从队列里取记录进行执行对账!
过程:线程T1和线程T2只有都生产完1条数据的时候,才能一起向下执行,也就是说,线程T1和线程T2要互相等待,步调要一致;同时当线程T1和T2都生产完一条数据的时候,还要能够通知线程T3执行对账操作。
难点:一个是线程T1和T2要做到步调一致,另一个是要能够通知到线程T3。
解决方案:用CyclicBarrier实现线程同步!(其实依然可以用一个计数器来解决这两个难点,计数器初始化为2,线程T1和T2生产完一条数据都将计数器减1,如果计数器大于0则线程T1或者T2等待。如果计数器等于0,则通知线程T3,并唤醒等待的线程T1或者T2,与此同时,将计数器重置为2,这样线程T1和线程T2生产下一条数据的时候就可以继续使用这个计数器了,但是java给我们提供了更加强大的CyclicBarrier)
实现:线程T1负责查询订单,当查出一条时,调用 barrier.await() 来将计数器减1,同时等待计数器变成0;线程T2负责查询派送单,当查出一条时,也调用 barrier.await() 来将计数器减1,同时等待计数器变成0;当T1和T2都调用 barrier.await() 的时候,计数器会减到0,此时T1和T2就可以执行下一条语句了,同时会调用barrier的回调函数来执行对账操作。非常值得一提的是,CyclicBarrier的计数器有自动重置的功能,当减到0的时候,会自动重置你设置的初始值
注意点:1. 执行回调的线程池Executor executor =Executors.newFixedThreadPool(1);大小必须是1!如果设置为多个,有可能会两个线程 A 和 B 同时执行对账操作,造成对账的数据不匹配(可能A线程取到俩个队列中不是一一对应的数据);所以1个线程实现生产数据串行执行,保证数据安全!
2.调用的check不可以直接调用--》必须要在线程池中,如果不用线程池,那执行check这个操作的回调线程就是CyclicBarrier把计数器减为0的线程,也就是查询线程在执行check操作,就会导致一个查询线程在等待,一个查询线程在执行check操作,导致效率降低
方案5:CompletableFuture优化方案三的写法---》方案三的写法也可以改为Future写法
总结
1.出现问题1:oom,如果队列生产太快,消费者(对账)来不及消费,就会出现oom
2.出现问题2: 生产环境下真的可以保证订单数据一一对应?