死磕JUC-妙用CountDownLatch和CycliBarrier协调多线程

    CountDownLatch和CycliBarrier本质其实就是计数器。具体的区别在于:

    CountDownLatch主要用于解决一个线程等待多个线程的场景。

    CycliBarrier是一组线程之间互相等待。

    除此之外,CountDownLatch的计数器是不能循环利用的,也就是说一旦计数器减到0,再有线程调用await(),该线程会直接通过。但CycliBarrier的计数器是可以循环利用的,而且具备自动重置的功能,一旦计数器减到0会自动重置到你设置的初始值,此外还可以设置回调函数。

    无论是物流还是电商,亦或是金融互联网,免不了谈的,就一点。如何确保账务正确。下面介绍一种常见的业务场景:比如用户通过在线商城进行下单,会生成电子订单,保存在订单库;之后物流会生成派送单给用户发货,派送单保存在派送单库,当然这个数据来源肯定是不同的系统,如物流系统告知我们的这样的。为了防止漏派或者重复派送,对账系统每天还会校验是否存在异常订单,具体业务先不展开描述。

    下面看下对账的基本处理逻辑,当然行业不同,处理方案也不尽相同,但都大同小异。目前对账系统的处理是首先查询订单,然后派送订单,之后对比订单和派送单,将不同的部分写入差异库中。下面分别说下对账系统抽象后的代码:


1、一般的处理流程:

    通常是在一个while循环,如果存在未对账的订单,那么就去:① 查询未对账的订单、② 查询派送单、③ 执行对账、④ 入库差异。

2 、利用并行优化对账系统:

    对于一般串行化的系统,性能优化首先想到的是能否利用多线程并行处理。

    在这个场景来说,①查询未对账与②查询派送单是否可以并行处理呢?显然是可以的,因为这两个操作没有先后顺序的依赖。这两个最耗时的操作并行后,你会发现同等时间内,并行执行的吞吐量近乎单线程的2倍,优化效果还是相对明显的。

    来说说具体的代码,我们可以新建两个线程来执行①和②的操作,之后主线程在T1和T2的join来实现等待,当T2和T2线程退出时,调用的主线程就会从阻塞态被唤醒,从而执行之后的③和④。

3、利用CountDownLatch实现线程等待:

    可能你也发现了,while循环里面每次都会创建新的线程,而创建线程可是个耗时的操作。所以,最好是创建出来的线程能够循环利用,这个时候就可以使用线程池了。

    我们可以在while循环外面,创建固定大小为2的线程池,之后在while循环里用,但是新的问题又出现了,我们之前是主线程中使用两个线程的join()来等待线程的退出,但是线程池的方案中,这两个线程根本就不会退出,所以join方法失效了。

    最简单的方法就是整一个计数器,执行完①②的操作后,计数器减为0。在主线程里,等待计数器等于0时,执行③④的操作。等待计数器其实就是一个条件变量,用管程实现也简单。

    因为Java中并发包已经提供了实现功能的工具类:CountDownLatch,在执行完①②的操作后,在主线程中使用latch.await()实现对计数器等于0的等待。

4 、进一步去优化性能,双队列实现完全并行:

    有小伙伴不难看出,前面两个查询操作已经并行了,但是③和④还是串行的,其实可以在执行对账的时候,可以同时去查询下一轮的查询操作。

    两次查询操作能够与对账实现并行,对账还依赖查询后的结果,明显有点生产者-消费者的意思了。我们完全可以通过两个队列、来分别保存查询的数据,即未对账订单和派送单。

    但是T1与T2这两个线程也得保持步调一致,即同时当T1和T2都生产完查询所需的数据后,再通知线程T3来进行对账的操作。

    使用CyclicBarrier来实现计数同步,当然你也可以自己利用一个计数器来操作,计数器初始化为2,线程T1和T2生产完一条数据都将计数器减1,如果计数器大于0则线程T1或者T2等待。如果计数器等于0,则通知线程T3,并唤醒等待的T1或者T2,与此同时,将计数器重置为2,这样T1和T2生产下一条数据的时候就可以继续使用这个计数器了。

    我们推荐你使用CyclicBarrier计数器,在初始化的同时,还可以传入一个回调函数,这样当计数器减为0时,会调用这个回调函数。同时CyclicBarrier还有自动重置的功能,当减到0的时候,会自动重置你设置的初始值,实在是方便多了。

下面给出最终的示例代码,欢迎大家多多探讨交流!需要注意的是,CyclicBarrier会在你的回调函数执行完才会唤醒等待的线程。


// 订单队列
Vector<P> pos;
// 派送单队列
Vector<D> dos;
// 执行回调的线程池 
Executor executor = 
  Executors.newFixedThreadPool(1);
final CyclicBarrier barrier =
  new CyclicBarrier(2, ()->{
    executor.execute(()->check());
  });
  
void check(){
  P p = pos.remove(0);
  D d = dos.remove(0);
  // 执行对账操作
  diff = check(p, d);
  // 差异写入差异库
  save(diff);
}
  
void checkAll(){
  // 循环查询订单库
  Thread T1 = new Thread(()->{
    while(存在未对账订单){
      // 查询订单库
      pos.add(getPOrders());
      // 等待
      barrier.await();
    }
  });
  T1.start();  
  // 循环查询运单库
  Thread T2 = new Thread(()->{
    while(存在未对账订单){
      // 查询运单库
      dos.add(getDOrders());
      // 等待
      barrier.await();
    }
  });
  T2.start();
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值