09 JUC 之 CountDownLatch

CountDownLatch说明

CountDownLatch是一个用来统计线程个数的工具,可以看作是个门闩(latch,门闩),必须等到线程齐了后才打开门闩。

CountDownLatch主要使用的方法是:

  • await,当前线程阻塞
  • countDown,当前线程释放锁。

CountDownLatch的两个应用场景:

1.乘车去买菜,也可看作是工人上工和下工。这种场景需要的使用两个CountDownLatch。代码如下:

private static final CountDownLatch people = new CountDownLatch(3);
private static final CountDownLatch car = new CountDownLatch(1);
static class Run3 implements Runnable {
    private String name;
    Run3(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        try {
            System.out.println(name + "上车");
            car.await();
            System.out.println(name + "去采购东西");
            // doWork部分,不同线程耗时不一样
            int second = new Random().nextInt(20);
            Thread.sleep(second * 1000);
            System.out.println(name + "采购东西完毕,回到车上");
            people.countDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public static void main(String[] args) throws InterruptedException {
    System.out.println("人员准备去采购");
    new Thread(new Run3("甲 ")).start();
    new Thread(new Run3("乙 ")).start();
    new Thread(new Run3("丙 ")).start();
    Thread.sleep(10 * 1000);
    // doWork
    car.countDown();
    System.out.println("车辆到达菜市场,人员下车");
    // 等待所有people完成采购
    people.await();
    System.out.println("车辆回家");
}

输出日志:
人员准备去采购
甲 上车
乙 上车
丙 上车
车辆到达菜市场,人员下车
乙 去采购东西
甲 去采购东西
丙 去采购东西
甲 采购东西完毕,回到车上
丙 采购东西完毕,回到车上
乙 采购东西完毕,回到车上
车辆回家

2.另一个案例是单独的CountDownLatch完成的,可以看作是上述代码中的people。这个案例是把一个大任务分成了多个小任务让不同的人(线程)去处理,等所有人(线程)完成后,这个任务才算完成。可以类比的案例是,赛场跑道上多个运动员比赛,每个运动员都完成了比赛。那这个比赛项目就结束了。又或者是一个宝藏密室需要多个钥匙都插入钥匙孔才能打开大门。

CountDownLatch的源码分析

CountDownLatch 内部也是有个Sync类继承了AbstractQueuedSynchronizer类,之前我们分析ReentrantLock、Semaphore时,发现这个父类他只提供模板方法。然而子类以不同的实现方式,来对应具体的业务案例。

CountDownLatch 的Sync类如下:

private static final class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 4982264981922014374L;

    Sync(int count) {
        setState(count);
    }

    int getCount() {
        return getState();
    }

    protected int tryAcquireShared(int acquires) {
        return (getState() == 0) ? 1 : -1;
    }

    protected boolean tryReleaseShared(int releases) {
        // Decrement count; signal when transition to zero
        for (;;) {
            int c = getState();
            if (c == 0)
                return false;
            int nextc = c-1;
            if (compareAndSetState(c, nextc))
                return nextc == 0;
        }
    }
}
// CountDownLatch的await方法
public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}
// AQS类的acquireSharedInterruptibly方法,也就是CountDownLatch实际执行的方法
public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}
private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
// CountDownLatch方法
public void countDown() {
    sync.releaseShared(1);
}
// AQS的releaseShared
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

从这简单的代码里,我们可以了解到以下内容:

  1. 通过构造方法,直接设置了AQS类中state的具体值,同时要求了执行线程的个数要和这个值保持一致。
  2. 主线程通过await方法被阻塞,说明state !=0,方法返回了-1。当前线程被放在了队列中,且被挂起了。
  3. 子线程调用countDown方法,实际上是将state值进行减少,因为每个线程只能减1,那么当线程个数等于初始state的时候,state才能被减到0,那么此时AQS的releaseShared方法里的tryReleaseShared(arg)方法得到了true,此时才能执行doReleaseShared方法,唤醒主线程。
  4. 结合案例,主线程和子线程其实是相对的概念。不能说main方法里就一定是主线程。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值