Java并发编程实战 - 并发 等待之CountDownLatch & CyclicBarrier

236 阅读2分钟
CountDownLatch & CyclicBarrier

CountDownLatch和CyclicBarrier是Java并发编程中用于任务编排的并发原语,它要解决的就是并发 - 等待的问题,在本文中,我们将以对账系统流程,来看看两者的使用场景。

对账系统流程

  1. 查询订单
  2. 查询派送单
  3. 对比订单和派送单,将差异写入差异库

Image.png

原型代码实现

while(存在未对账订单){
        // 查询未对账订单
        pos = getPOrders();
        // 查询派送单
        dos = getDOrders();
        // 执行对账操作
        diff = check(pos, dos);
        // 差异写入差异库
        save(diff);
        }

优化思路
单线程变多线程执行任务,提高并行度。

代码优化1
CountDownLatch
将1,2交给线程池并行执行,使用countDownLatch来实现1,2的阻塞和同步

Executor executor =
            Executors.newFixedThreadPool(2);

while(存在未对账订单){
        // 计数器初始化为 2
        CountDownLatch latch =
            new CountDownLatch(2);
        // 查询未对账订单
        executor.execute(()-> {
            pos = getPOrders();
            latch.countDown();
        });
        // 查询派送单
        executor.execute(()-> {
            dos = getDOrders();
            latch.countDown();
        });

        // 等待两个查询操作结束
        latch.await();

        // 执行对账操作
        diff = check(pos, dos);
        // 差异写入差异库
        save(diff);
        }

代码优化2
将3,4也交给多线程并行执行,我们引入双队列,订单和派送单完成后都插入到队列中,T1, T2保持一致

Image.png

3进行check操作,需要等待T1, T2先完成Image.png

CyclicBarrier
用 CyclicBarrier 实现线程同步, 与CountDownLatch不同的是CyclicBarrier会自动重置

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

// 订单队列
Vector<P> pos;
// 派送单队列
Vector<D> dos;

// 执行回调的线程池
Executor executor =
        Executors.newFixedThreadPool(1);

//用 CyclicBarrier 实现线程同步
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();
}

总结\

  1. CountDownLatch 主要用来解决一个线程等待多个线程的场景。
  2. CyclicBarrier一组线程之间互相等待,且可以循环利用