快速上手CountDownLatch

451 阅读1分钟

快速上手CountDownLatch

CountDownLatch可以使一个线程在等待其他多个线程各自执行完毕后再执行。也就是说countdownlatch可以实现线程等待。

实现原理

countdownlatch 内部定义了一个计数器和一个等待队列,在计数器递减到0之前,等待队列里的现成是阻塞状态,当计数器到0后,会去唤醒此时等待队列里所有的阻塞线程

主要api

CountDownLatch(int count); //设置计数器的count值await();//阻塞当前线程,加入其进入阻塞队列await(long timeout, TimeUnit unit); //阻塞当前线程,当超时时间到时,该线程开始执行countDown(); // 计数器减1

api的实现

创建计数器

  public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }

阻塞线程

     public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
    
    
     public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        //锁重入次数大于0 则新建节点加入阻塞队列,挂起当前线程
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }

计时器递减

public final boolean releaseShared(int arg) {
        //递减锁的重入次数
        if (tryReleaseShared(arg)) {
            doReleaseShared();//唤醒队列所有阻塞的节点
            return true;
        }
        return false;
    }
 private void doReleaseShared() {
        //唤醒所有阻塞队列里面的线程
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {//节点是否在等待唤醒状态
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))//修改状态为初始
                        continue;
                    unparkSuccessor(h);//成功则唤醒线程
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }

使用场景

countdownlatch 可以用来在业务中,需要等待多个操作执行完的才触发返回的case。

举个实际点的例子,淘宝客服这边使用的客服工作台,用户进线后,需要根据用户发送过来的订单卡片信息,来给客服展现相关的用户信息、用户画像、历史售后以及订单商品详情等等的信息,辅助客服来解决用户的问题。这些信息都来源于不同的内部技术组,比如拿订单信息来说,就可能在使用订单号获取订单信息的这个服务化接口实现中,调用订单组的接口提供订单主体信息,调用商品组的接口获取商品的信息,调用仓储组的接口获取订单商品的仓库信息,调用库存组获取该商品当前的库存信息等等,在一个接口中,如果一个一个接口的调用写下来,那么这些接口的调用就变成了串行,服务化的接口大部分都耗费在网络传输上,所以,就要想办法如何将这些接口的调用并行化,这里就可以使用countdownlatch来实现了,并行的调用各个组的服务化接口获取结果,当各个组的服务化接口全部返回后,触发countdownlatch计数器递减到0,此时,整个客服工作台的接口返回,这样就提供了整个接口的响应时间。

代码实现

@Service
@AllArgConstructor
public class OrderFacade {

    private OrderCompose orderCompose;
    
    private ItemCompose itemCompose;
    
    private WarehouseCompose warehouseCompose;
    
    private StorageCompose storageCompose;

    public Order getOrderItemByOrderItemId(String orderId){
        
        OrderResult result = null;
        
        Executor executor = Executors.newFixedThreadPool(4);
        
        CountDownLatch latch = new CountDownLatch(4);
        
        executor.execute(() -> {
            Order order = orderCompose.getOrder(orderId);
            latch.countDown();
        });
    
         executor.execute(() -> {
            Item item = itemCompose.getSku(orderId);
            latch.countDown();
        });
        
         executor.execute(() -> {
            Warehouse warehouse = warehouseCompose.getWarehouse(orderId);
            latch.countDown();
        });
        
         executor.execute(() -> {
            Storage storage = storageCompose.getStorage(orderId);
            latch.countDown();
        });
        
        latch.await();
        
        result = buildOrder(order, item, warehouse, storage);
        
        return result;
    }
}