Java并发编程核心方法与框架-CountDownLatch的使用

来源:互联网 发布:2017网络综艺发展趋势 编辑:程序博客网 时间:2024/06/06 05:08

Java多线程编程中经常会碰到这样一种场景:某个线程需要等待一个或多个线程操作结束(或达到某种状态)才开始执行。比如裁判员需要等待运动员准备好后才发送开始指令,运动员要等裁判员发送开始指令后才开始比赛。

public class Player implements Runnable {    private int id;    private CountDownLatch begin;    private CountDownLatch end;    public Player(int i, CountDownLatch begin, CountDownLatch end) {        super();        this.id = i;        this.begin = begin;        this.end = end;    }    @Override    public void run() {        try {            begin.await();// 等待begin的状态为0时开始            System.out.println("Play" + id + "开始时间:" + System.currentTimeMillis());            Thread.sleep((long) (Math.random() * 100));// 随机分配时间,即运动员完成时间            System.out.println("Play" + id + " arrived.");        } catch (InterruptedException e) {            e.printStackTrace();        } finally {            end.countDown();// 使end状态减1,最终减至0        }    }}public class CountDownLatchDemo {    private static final int PLAYER_AMOUNT = 5;    public static void main(String[] args) {        //对于每位运动员,CountDownLatch减1后即结束比赛        CountDownLatch begin = new CountDownLatch(1);// 相当于裁判员        //对于整个比赛,所有运动员结束后才算结束        CountDownLatch end = new CountDownLatch(PLAYER_AMOUNT);        Player[] plays = new Player[PLAYER_AMOUNT];        for (int i = 0; i < PLAYER_AMOUNT; i++) {            plays[i] = new Player(i + 1, begin, end);        }        // 设置特定的线程池,大小为5        ExecutorService exe = Executors.newFixedThreadPool(PLAYER_AMOUNT);        for (Player p : plays) {            exe.execute(p);// 分配线程        }        System.out.println("Race begins!");        begin.countDown();        try {            end.await();// 等待end状态变为0,即为比赛结束        } catch (InterruptedException e) {            e.printStackTrace();        } finally {            System.out.println("Race ends!");        }        exe.shutdown();    }}

程序运行结果如下:

Race begins!Play1开始时间:1469438682994Play4开始时间:1469438682994Play2开始时间:1469438682994Play3开始时间:1469438682994Play5开始时间:1469438682994Play3 arrived.Play4 arrived.Play1 arrived.Play5 arrived.Play2 arrived.Race ends!

五个线程在main线程执行begin.countDown()后同时开始执行,每个线程执行完毕后都会执行end.countDown(),main线程等待end状态为0时停止执行。

再看一个例子:

public class CountDownLatchTest {    // 模拟了100米赛跑,10名选手已经准备就绪,只等裁判一声令下。当所有人都到达终点时,比赛结束。    public static void main(String[] args) throws InterruptedException {        // 开始的倒数锁         final CountDownLatch begin = new CountDownLatch(1);          // 结束的倒数锁         final CountDownLatch end = new CountDownLatch(10);          // 十名选手         final ExecutorService exec = Executors.newFixedThreadPool(10);          for (int index = 0; index < 10; index++) {            final int NO = index + 1;              Runnable run = new Runnable() {                public void run() {                      try {                          // 如果当前计数为零,则此方法立即返回。                        // 等待                        begin.await();                          Thread.sleep((long) (Math.random() * 10000));                          System.out.println("No." + NO + " arrived");                      } catch (InterruptedException e) {                      } finally {                          // 每个选手到达终点时,end就减一                        end.countDown();                    }                  }              };              exec.submit(run);        }          System.out.println("Game Start");          // begin减一,开始游戏        begin.countDown();          // 等待end变为0,即所有选手到达终点        end.await();          System.out.println("Game Over");          exec.shutdown();      }}

程序运行结果如下:

Game StartNo.10 arrivedNo.5 arrivedNo.8 arrivedNo.9 arrivedNo.1 arrivedNo.2 arrivedNo.3 arrivedNo.6 arrivedNo.4 arrivedNo.7 arrivedGame Over

程序执行原理和前一个例子相同。

CountDownLatch工作原理相对简单,可以简单看成一个倒计时器,在构造方法中指定初始值,每次调用countDown()方法时讲计数器减1,而await()会等待计数器变为0。CountDownLatch关键接口如下

  • countDown() 如果当前计数器的值大于1,则将其减1;若当前值为1,则将其置为0并唤醒所有通过await等待的线程;若当前值为0,则什么也不做直接返回。
  • await() 等待计数器的值为0,若计数器的值为0则该方法返回;若等待期间该线程被中断,则抛出InterruptedException并清除该线程的中断状态。
  • await(long timeout, TimeUnit unit) 在指定的时间内等待计数器的值为0,若在指定时间内计数器的值变为0,则该方法返回true;若指定时间内计数器的值仍未变为0,则返回false;若指定时间内计数器的值变为0之前当前线程被中断,则抛出InterruptedException并清除该线程的中断状态。
  • getCount() 读取当前计数器的值,一般用于调试或者测试。

一个完整的比赛流程:

public class MyThread extends Thread {    private static final int THREAD_NUM = 10;        private CountDownLatch comingTag;    private CountDownLatch waitTag;    private CountDownLatch waitRunTag;    private CountDownLatch beginTag;    private CountDownLatch endTag;    public MyThread(CountDownLatch comingTag, CountDownLatch waitTag, CountDownLatch waitRunTag, CountDownLatch beginTag, CountDownLatch endTag) {        super();        this.comingTag = comingTag;        this.waitTag = waitTag;        this.waitRunTag = waitRunTag;        this.beginTag = beginTag;        this.endTag = endTag;    }        @Override    public void run() {        try {            System.out.println(Thread.currentThread().getName() + "运动员正在赶往起点。。。");            Thread.sleep((int)(Math.random() * 10000));            comingTag.countDown();            System.out.println(Thread.currentThread().getName() + "等待裁判说准备。。。");            waitTag.await();            System.out.println(Thread.currentThread().getName() + "预备。。。");            Thread.sleep((int)(Math.random() * 10000));            waitRunTag.countDown();            beginTag.await();            System.out.println(Thread.currentThread().getName() + "起跑。。。" + System.currentTimeMillis());            Thread.sleep((int)(Math.random() * 10000));            System.out.println(Thread.currentThread().getName() + "到达终点。。。");            endTag.countDown();        } catch (Exception e) {            e.printStackTrace();        }    }        public static void main(String[] args) {        try {            CountDownLatch comingTag = new CountDownLatch(THREAD_NUM);            CountDownLatch waitTag = new CountDownLatch(1);            CountDownLatch waitRunTag = new CountDownLatch(THREAD_NUM);            CountDownLatch beginTag = new CountDownLatch(1);            CountDownLatch endTag = new CountDownLatch(THREAD_NUM);                        MyThread[] threads = new MyThread[THREAD_NUM];            for (int i = 0; i < threads.length; i++) {                threads[i] = new MyThread(comingTag, waitTag, waitRunTag, beginTag, endTag);                threads[i].start();            }            System.out.println("裁判员正在等待选手到场。。。");            comingTag.await();            System.out.println("裁判员看到全部运动员已到场。。。准备5秒。。。");            Thread.sleep(5000);            waitTag.countDown();            System.out.println("各就各位,预备。。。");            waitRunTag.await();            System.out.println("发令枪响起。。。");            beginTag.countDown();            endTag.await();            System.out.println("所有运动员到达终点,比赛结束。。。");        } catch (Exception e) {            e.printStackTrace();        }    }}

程序运行结果如下:

Thread-0运动员正在赶往起点。。。Thread-4运动员正在赶往起点。。。Thread-3运动员正在赶往起点。。。Thread-2运动员正在赶往起点。。。Thread-1运动员正在赶往起点。。。Thread-6运动员正在赶往起点。。。Thread-5运动员正在赶往起点。。。Thread-7运动员正在赶往起点。。。Thread-8运动员正在赶往起点。。。裁判员正在等待选手到场。。。Thread-9运动员正在赶往起点。。。Thread-0等待裁判说准备。。。Thread-5等待裁判说准备。。。Thread-3等待裁判说准备。。。Thread-7等待裁判说准备。。。Thread-1等待裁判说准备。。。Thread-6等待裁判说准备。。。Thread-4等待裁判说准备。。。Thread-9等待裁判说准备。。。Thread-8等待裁判说准备。。。Thread-2等待裁判说准备。。。裁判员看到全部运动员已到场。。。准备5秒。。。各就各位,预备。。。Thread-0预备。。。Thread-5预备。。。Thread-3预备。。。Thread-7预备。。。Thread-4预备。。。Thread-8预备。。。Thread-6预备。。。Thread-1预备。。。Thread-2预备。。。Thread-9预备。。。发令枪响起。。。Thread-4起跑。。。1469453409388Thread-5起跑。。。1469453409388Thread-6起跑。。。1469453409388Thread-7起跑。。。1469453409388Thread-2起跑。。。1469453409388Thread-9起跑。。。1469453409388Thread-0起跑。。。1469453409388Thread-1起跑。。。1469453409388Thread-8起跑。。。1469453409388Thread-3起跑。。。1469453409388Thread-6到达终点。。。Thread-5到达终点。。。Thread-2到达终点。。。Thread-1到达终点。。。Thread-3到达终点。。。Thread-0到达终点。。。Thread-9到达终点。。。Thread-7到达终点。。。Thread-8到达终点。。。Thread-4到达终点。。。所有运动员到达终点,比赛结束。。。



0 0