[笔记][Java7并发编程实战手册]3.4 等待多个并发事件的完成CountDownLatch倒计数闭锁

[笔记][Java7并发编程实战手册]系列目录


简介

本文学习CountDownLatch 倒计数闭锁。
本人英文不好,靠机器翻译,然后有一段很形象的描述,让我把它叫为倒计数

用给定的计数 初始化 CountDownLatch。由于调用了 countDown() 方法,所以在当前计数到达零之前,await 方法会一直受阻塞。之后,会释放所有等待的线程,await 的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。 - jdk1.6里面的文档

可以看出,是因为会倒计数,而且计数器到0之后, 就不能再次使用该对象了(自断后路),所以称为倒计数闭锁


CountDownLatch

是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
假如说:有a、b、c 三个线程,c 需要a,b线程的结果,所以C 需要等待 a,b 线程都执行完了,b 才能拿到正确的结果。 就是该类的一个使用场景

  1. countDown() 来计数(计时)
  2. await() 创建屏障点,等待计数到0的时候,await()方法后面的代码才执行。(换句话说,计数线程一起冲破这个屏障)
  3. getCount(): 是获取当前计数,不是总数
  4. 是辅助类! 不保证同步。

原理:
CountDownLatch是通过“共享锁”实现的。在创建CountDownLatch中时,会传递一个int类型参数count,该参数是“锁计数器”的初始状态,表示该“共享锁”最多能被count给线程同时获取。当某线程调用该CountDownLatch对象的await()方法时,该线程会等待“共享锁”可用时,才能获取“共享锁”进而继续运行。而“共享锁”可用的条件,就是“锁计数器”的值为0!而“锁计数器”的初始值为count,每当一个线程调用该CountDownLatch对象的countDown()方法时,才将“锁计数器”-1;通过这种方式,必须有count个线程调用countDown()之后,“锁计数器”才为0,而前面提到的等待线程才能继续运行!


示例

场景:密室大门将在七颗龙珠都被镶嵌到大门上的机关上时,密室大门才会打开。

下面的示例:讲述了一个密室(线程),和7个工匠(线程)镶嵌龙珠, 最后密室大门被打开,守卫(主线程)复活的故事

/**
 * Created by zhuqiang on 2015/8/19 0019.
 */
public class Client {
    public static void main(String[] args) {
        final CountDownLatch cd = new CountDownLatch(7);  //创建辅助类7个倒计数
        final Adytum adytum = new Adytum(cd);
        new Thread(adytum).start();
        for (int i = 0; i < 7; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    adytum.inlay();
                }
            }).start();
        }

        // 主线程 来模拟守卫吧。
        try {
            cd.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("守卫也复活了!");
    }

}

//密室
class Adytum implements Runnable {
    private CountDownLatch cd;
    private long num = 7;  // 记录剩余未被镶嵌的龙珠 ,用来对比和getCount返回的是否相等
    private ReentrantLock lock = new ReentrantLock();   //创建同步锁,保证 inlay 中的资源计数 被正确计算。

    public Adytum(CountDownLatch cd) {
        this.cd = cd;
    }

    // 为密室大门服务的线程
    @Override
    public void run() {
        System.out.println("欢迎来到密室,密室正等待被打开...");
        try {
            cd.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.printf("当前龙珠已镶嵌%d颗,密室被打开了!欢呼吧\n",num);
    }

    /**
     * 镶嵌龙珠到大门上
     */
    public void inlay() {
        try {
            lock.lock();  // 把这里的锁去掉,能看到错误的计算数据,说明了CountDownLatch:不保证同步
            long count = cd.getCount();  //返回当前还剩余计数
            long time = (long) (Math.random() * 10);  //模拟镶嵌了多少时间
            System.out.printf("%s把龙珠镶嵌到了大门上,还有%d个龙珠未被镶嵌,计数是否和getCount相等:%s,此次镶嵌耗时:%d 秒\n", Thread.currentThread().getName(), count,count==num,time);
            num--;
            TimeUnit.SECONDS.sleep(time);  //休眠辅助类。按指定单位休眠
            cd.countDown();  //锁 保证是这个临界区内的代码同步,线程互斥,计算的结果正确被刷到主内存中, 不是说countDown方法需要来同步
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

未加锁的某一次运行结果:
把 lock.lock(); 加锁相关的代码注释掉。

欢迎来到密室,密室正等待被打开...
Thread-1把龙珠镶嵌到了大门上,还有7个龙珠未被镶嵌,计数是否和getCount相等:true,此次镶嵌耗时:5 秒
Thread-2把龙珠镶嵌到了大门上,还有7个龙珠未被镶嵌,计数是否和getCount相等:true,此次镶嵌耗时:2 秒
Thread-4把龙珠镶嵌到了大门上,还有7个龙珠未被镶嵌,计数是否和getCount相等:true,此次镶嵌耗时:0 秒
Thread-3把龙珠镶嵌到了大门上,还有7个龙珠未被镶嵌,计数是否和getCount相等:true,此次镶嵌耗时:1 秒
Thread-6把龙珠镶嵌到了大门上,还有7个龙珠未被镶嵌,计数是否和getCount相等:true,此次镶嵌耗时:7 秒
Thread-5把龙珠镶嵌到了大门上,还有7个龙珠未被镶嵌,计数是否和getCount相等:true,此次镶嵌耗时:9 秒
Thread-7把龙珠镶嵌到了大门上,还有7个龙珠未被镶嵌,计数是否和getCount相等:true,此次镶嵌耗时:0 秒
守卫也复活了!
当前龙珠已镶嵌0颗,密室被打开了!欢呼吧

说明:能看到错误的计算数据,全是还有7个龙珠,说明了CountDownLatch:不保证同步,只是达到 让一个线程或则多个线程等待


加锁的正确结果

欢迎来到密室,密室正等待被打开...
Thread-1把龙珠镶嵌到了大门上,还有7个龙珠未被镶嵌,计数是否和getCount相等:true,此次镶嵌耗时:2 秒
Thread-2把龙珠镶嵌到了大门上,还有6个龙珠未被镶嵌,计数是否和getCount相等:true,此次镶嵌耗时:0 秒
Thread-3把龙珠镶嵌到了大门上,还有5个龙珠未被镶嵌,计数是否和getCount相等:true,此次镶嵌耗时:5 秒
Thread-4把龙珠镶嵌到了大门上,还有4个龙珠未被镶嵌,计数是否和getCount相等:true,此次镶嵌耗时:5 秒
Thread-5把龙珠镶嵌到了大门上,还有3个龙珠未被镶嵌,计数是否和getCount相等:true,此次镶嵌耗时:0 秒
Thread-6把龙珠镶嵌到了大门上,还有2个龙珠未被镶嵌,计数是否和getCount相等:true,此次镶嵌耗时:2 秒
Thread-7把龙珠镶嵌到了大门上,还有1个龙珠未被镶嵌,计数是否和getCount相等:true,此次镶嵌耗时:5 秒
当前龙珠已镶嵌0颗,密室被打开了!欢呼吧
守卫也复活了!

说明:有密室,和守卫两个线程等待7个工匠线程把龙珠镶嵌完毕后,密室被打开,守卫也复活,得到了我们想要的正确结果

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值