欢迎使用CSDN-markdown编辑器

来源:互联网 发布:淘宝怎么删除好评价 编辑:程序博客网 时间:2024/06/17 06:38

**

CountDownLatch类应用详解

**
JAVA并发包中控制线程的同步的工具类CountDownLatch使线程在同步的处理上更加灵活,比如支持同步计数重置、等待同步线程个数等常见功能,这个工具类把同步和线程“组团”做任务完美进行了支持。

1. CountDownLatch类初印象
单词Latch中文意思是门闩,因而具有“门锁”的功能,所以当门没有打开的时候,N个人是不能进入屋内的,也就意味着N个线程是不能继续向下运行的,正是由于 CountDownLatch支持这样的特性,所以可以控制线程执行任务的时机,使线程以“组团”的方式一起执行任务。

2. CountDownLatch类概述及原理
此类基本核心及特色在count计数上。核心主要是对count进行判断,count计数不为0时,则呈wait状态,如果为0时则继续运行。主要通过await()和countDown()方法分别实现线程等待与继续运行的效果。await()方法在本线程中判断计数是否为0,countDown()方法可以在其他线程中被调用将count计数减1,当计数减到0时,则可使呈等待的线程继续运行。另外可以用getCount()方法获得当前count的数目。
CountDownLatch 不要求调用 countDown() 方法的线程等到计数到达零时才继续,而在所有线程都能通过之前,它只是阻止任何线程继续通过一个await()。

需要说明的是在此类中计数无法被重置,如果需重置计数则需考虑用CyclicBarrier类。

3. CountDownLatch类作用以及应用场合

它的使用主要分两方面:

(1)将计数 1 初始化的CountDownLatch 用作一个简单的开/关锁存器,或入口,在通过调用countDown() 的线程打开入口前,所有调用await 的线程都一直在入口处等待。

应用场合举例:
(1)我们需要解析一个Excell里边多个sheet的数据,此时每个线程解析一个sheet里边的数据,解析完一个sheet,调用countDownD方法使计数count减一,等到所有的sheet都解析完之后即count被减为0,不再阻塞,在主线程中提示解析完成。
(2)裁判等待所有运动员都准备好之后再开始打发令枪、当所有运动员都到达终点后再开始统计名次并公布。以及其他需要组团、都满足某一条件才可以继续往下执行的应用场合。
4. CountDownLatch类应用实例
模拟完整的比赛流程

> 代码主要由两个部分组成

主要框架:
整体流程图
第一部分为一个线程,此线程的私有成员主要有:

            private CountDownLatch comingTag;    //裁判等待所有运动员到            private CountDownLatch waitTag;        //等待裁判说开始            private CountDownLatch waitRunTag;   //等待起跑            private CountDownLatch beginTag;      //起跑            private CountDownLatch endTag;         //所有运动员到达终点 

在此线程的run函数中:

    System.out.println("运动员正以不同的速度往比赛地点走!");    Thread.sleep((int)(Math.random()*10000));//用线程睡随机数的时间模拟       运动员以不同速度到达比赛地点的场景    System.out.println(Thread.currentThread().getName()+"到达跑点了!");//输出第几位运动员到达比赛地点   comingTag.countDown();//到达比赛地点一运动员,需要等待的运动员的个数减1   System.out.println("等待裁判说准备!");   waitTag.await();//判断waitTag是否为0,不为0就阻塞,不进入下一阶段   System.out.println("裁判说各就各位,大家进行起跑热身!");//此时大家都准备好   Thread.sleep((int)(Math.random()*10000));//用线程睡随机数的时间模拟       运动员准备起跑的不同时间   waitRunTag.countDown();//每当有一个运动员准备好,准备起跑的运动员个数减1   beginTag.await();//判断waitTag是否为0,不为0就阻塞,不进入下一阶段   System.out.println(Thread.currentThread().getName()+"运动员起跑,并且跑赛过程用时不确定");  Thread.sleep((int)(Math.random()*10000));//用线程睡随机数的时间模拟       运动员比赛所用的不同时间  endTag.countDown();//每当有一个运动员到达终点,正在跑的运动员个数减1  System.out.println(Thread.currentThread().getName()+"运动员到达终点!");

第二部分主函数,需要初始化的对象有:

CountDownLatch comingTag=new CountDownLatch(10);//初始化个数为10的comingTag对象,代表正赶来跑点的不同运动员CountDownLatch waitTag=new CountDownLatch(1);//初始化个数为1的waitTag对象,当所有的运动员都到达比赛地点的时候,count减1,进入比赛下一流程CountDownLatch waitRunTag=new CountDownLatch(10);//初始化个数为10的waitRunTag对象,代表正在为起跑做热身的不同的运动员CountDownLatch beginTag=new CountDownLatch(1);//初始化个数为1的beginTag对象,当所有的运动员都准备好比赛的时候,count减1,进入比赛下一流程CountDownLatch endTag=new CountDownLatch(10);//初始化个数为10的endTag对象,代表陆续到达终点的不同运动员

创建10个线程,代表10位运动员·

  MyThread [] tArray=new MyThread[10];  for(int i=0;i<tArray.length;i++){  tArray[i]=new MyThread(comingTag,waitTag,waitRunTag,beginTag,endTag);          tArray[i].start();      }

用await方法和countDown方法配合使用,控制线程的阻塞和运行

      System.out.println("裁判员正在等待选手来!");      comingTag.await();//判断count是否为0,若为0,则代表所有运动员都到达跑点,否则阻塞在这个地方,不进入下一比赛流程      System.out.println("所有运动员都来了,可以开始做准备活动了");      waitTag.countDown();//大家都到了,waitTag由1变为0,比赛进入下一阶段      System.out.println("各就各位!!");      waitRunTag.await();//判断count是否为0,若为0,则代表所有运动员都到准备好,否则阻塞在这个地方,不进入下一比赛流程      Thread.sleep(2000);      System.out.println("发令枪响!");      beginTag.countDown();//所有的运动员都已经准备好,beginTag有1变为0,比赛进入下一阶段      endTag.await();//判断count是否为0,若为0,则代表所有运动员都已经到达终点,否则阻塞在这个地方,不进入下一比赛流程      System.out.println("所有运动员到达,统计比赛名次!");

运行结果:
运动员正在赶来:
运动员陆续到达跑点
运动员起跑热身
运动员陆续开跑
运动员陆续到达终点

5. CountDownLatch类实现原理及源代码剖析

实现原理

ASQ状态图(以2为例):

状态图

实现原理图

这里写图片描述

源代码剖析

(1) 首先分析它的功能:

A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.//即说主线程在等待所有其它的子线程完成后再往下执行。

(2)它所提供的方法如下:
所有方法的图片

(3)它内部有一个辅助类

 *      +------+  prev +-----+       +-----+     * head |      | <---- |     | <---- |     |  tail     *      +------+       +-----+       +-----+
  Synchronization control For CountDownLatch.  Uses AQS state to represent count.
 private static final class Sync extends AbstractQueuedSynchronizer {       private static final long serialVersionUID = 4982264981922014374L;//此处在new CountDownLatch时会调用        Sync(int count) {            setState(count);//初始化count的个数        int getCount() {            return getState();//返回count的个数        }        protected int tryAcquireShared(int acquires) {            return (getState() == 0) ? 1 : -1;            //如果count为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;            }        }    }

(3)CountDownLatch构造方法

  public CountDownLatch(int count) {        if (count < 0) throw new IllegalArgumentException("count < 0");        this.sync = new Sync(count);    }//Constructs a  CountDownLatch  initialized with the given count即构造一个count的CountDownLatch类

(4)await()方法

public void await() throws InterruptedException {        sync.acquireSharedInterruptibly(1);    }//Causes the current thread to wait until the latch has counted down to 0即等待现在的线程直到count减为0

(5)countDown()方法

 public void countDown() {        sync.releaseShared(1);    }//ecrements the count of the latch, releasing all waiting threads if the count reaches zero即将count减1

(6)getCount()方法

 public long getCount() {        return sync.getCount();    }//Returns the current count即返回现在的count数目

(7)toString()方法

 public String toString() {        return super.toString() + "[Count = " + sync.getCount() + "]";    }//Returns a string identifying this latch, as well as its state.即返回一个此latch包括它的状态

6. CountDownLatch类总结
总之,CountDownLatch最重要的方法是countDown()和await(),前者主要是倒数一次,后者是等待倒数到0,如果没有到达0,就只有阻塞等待。本篇从CountDownLatch入手,也了讲解AQS中的共享锁模式,建议大家一起学习其设计的思路,不要陷入所有条件处理细节中,多线程环境中,把握好使用环境以及方法,恰当的运用不同的java并发编程核心代码和框架。
7. 参考文献
[1]童云兰等译.Java并发编程实战[M]. 北京:机械工业出版社,2012.
[2] 张振华 .Java并发编程从入门到精通[M].北京:清华大学出版社,2015.
[3] 方腾飞,魏鹏,程晓明.Java并发编程的艺术[M].北京:机械工业出版社,2015.
[4] 高洪岩.Java并发编程核心框架与方法[M].北京:机械工业出版社,2015.
[5] Eclipse里设置查看Java源码的方法http://blog.csdn.net/Willtom/article/details/52264036

如有错误,请大家指正