zoukankan      html  css  js  c++  java
  • Java并发编程核心方法与框架-CountDownLatch的使用

    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开始时间:1469438682994
    Play4开始时间:1469438682994
    Play2开始时间:1469438682994
    Play3开始时间:1469438682994
    Play5开始时间:1469438682994
    Play3 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 Start
    No.10 arrived
    No.5 arrived
    No.8 arrived
    No.9 arrived
    No.1 arrived
    No.2 arrived
    No.3 arrived
    No.6 arrived
    No.4 arrived
    No.7 arrived
    Game 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起跑。。。1469453409388
    Thread-5起跑。。。1469453409388
    Thread-6起跑。。。1469453409388
    Thread-7起跑。。。1469453409388
    Thread-2起跑。。。1469453409388
    Thread-9起跑。。。1469453409388
    Thread-0起跑。。。1469453409388
    Thread-1起跑。。。1469453409388
    Thread-8起跑。。。1469453409388
    Thread-3起跑。。。1469453409388
    Thread-6到达终点。。。
    Thread-5到达终点。。。
    Thread-2到达终点。。。
    Thread-1到达终点。。。
    Thread-3到达终点。。。
    Thread-0到达终点。。。
    Thread-9到达终点。。。
    Thread-7到达终点。。。
    Thread-8到达终点。。。
    Thread-4到达终点。。。
    所有运动员到达终点,比赛结束。。。
    
  • 相关阅读:
    [Noip2010]乌龟棋
    vijos次小生成树
    hdu3579-Hello Kiki-(扩展欧几里得定理+中国剩余定理)
    hdu1573-X问题-(扩展欧几里得定理+中国剩余定理)
    poj2115-Looooops-(扩展欧几里得定理)
    hdu2669-Romantic-(扩展欧几里得定理)
    poj1061-青蛙的约会-(贝祖定理+扩展欧几里得定理+同余定理)
    hdu1576-A/B-(同余定理+乘法逆元+费马小定理+快速幂)
    hdu4497-GCD and LCM-(欧拉筛+唯一分解定理+组合数)
    hdu3189-Just Do It-(埃氏筛+唯一分解定理)
  • 原文地址:https://www.cnblogs.com/umgsai/p/5671636.html
Copyright © 2011-2022 走看看