[笔记][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颗。密室被打开了!欢呼吧 ",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 秒 ", 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个工匠线程把龙珠镶嵌完毕后,密室被打开,守卫也复活,得到了我们想要的正确结果

原文地址:https://www.cnblogs.com/cxchanpin/p/7084877.html