多线程基础(七)CountDownLatch与CyclicBarrier

1. CountDownLatch
CountDownLatch的作用是,新建CountDownLatch时可以指定count计数器数,每调用一次countDownLatch.countDown();计数器减一,countDownLatch.await();方法会阻塞知道计数器值为0才会往下执行。
一般可用于某n个线程等待其它n个线程执行完毕才执行。如下示例

public class CountDownLatchTest {
    public static void main(String[] args) throws Exception{
        CountDownLatch countDownLatch = new CountDownLatch(1);
        new Thread(){
            @Override
            public void run() {
                try {
                    Thread.sleep(5000L);
                    System.out.println("自定义线程执行完了");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    countDownLatch.countDown();
                }
            }
        }.start();
        countDownLatch.await();
        System.out.println("主线程执行完了");
    }
}

打印结果:

自定义线程执行完了
主线程执行完了

如果去掉CountDownLatch的相关代码,打印结果会变成

主线程执行完了
自定义线程执行完了

2. CyclicBarrier 是什么?
从字面上的意思可以知道,这个类的中文意思是“循环栅栏”。大概的意思就是一个可循环利用的屏障。
它的作用就是会让所有线程都等待完成后才会继续下一步行动。
举个例子,就像生活中我们会约朋友们到某个餐厅一起吃饭,有些朋友可能会早到,有些朋友可能会晚到,但是这个餐厅规定必须等到所有人到齐之后才会让我们进去。这里的朋友们就是各个线程,餐厅就是 CyclicBarrier。
怎么使用 CyclicBarrier
2.1 构造方法
public CyclicBarrier(int parties)
public CyclicBarrier(int parties, Runnable barrierAction)

解析:

parties 是参与线程的个数
第二个构造方法有一个 Runnable 参数,这个参数的意思是最后一个到达线程要做的任务

2.2 重要方法
public int await() throws InterruptedException, BrokenBarrierException
public int await(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException

解析:

线程调用 await() 表示自己已经到达栅栏
BrokenBarrierException 表示栅栏已经被破坏,破坏的原因可能是其中一个线程 await() 时被中断或者超时

3. CyclicBarrier案例----两个人都到了才能开饭

public class CountDownLatchTest {
    public static void main(String[] args) throws Exception {
        // 发音[ˈsaɪklɪk], [ˈbæriə(r)]
        CyclicBarrier cyclicBarrier = new CyclicBarrier(2);
        new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000L);
                    System.out.println("线程 1:我到了" + System.currentTimeMillis() / 1000);
                    cyclicBarrier.await();
                    System.out.println("线程 1:我要开吃了" + System.currentTimeMillis() / 1000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }.start();
        new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5000L);
                    System.out.println("线程 2:我到了" + System.currentTimeMillis() / 1000);
                    cyclicBarrier.await();
                    System.out.println("线程 2:我要开吃了" + System.currentTimeMillis() / 1000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }.start();

    }
}

执行结果:

线程 1:我到了1566052287
线程 2:我到了1566052291
线程 2:我要开吃了1566052291
线程 1:我要开吃了1566052291

4. 中断
多个线程在等待栅栏时某个线程发生了中断,则该线程抛出InterruptedException,由于栅栏被破坏其它线程抛出BrokenBarrierException
超时
如果多个线程等待栅栏期间,某一个线程调用的是cyclicBarrier.await(1, TimeUnit.SECONDS);,在超时时间内没有等到其它线程都到达栅栏,则该线程抛出java.util.concurrent.TimeoutException,其它线程由于栅栏被破坏抛出BrokenBarrierException

5. CyclicBarrier循环使用
CyclicBarrier内部相当于有个计数器(构造方法传入的),每次调用await();后,计数器会减1,并且await()方法会让当前线程阻塞,等待计数器减为0的时候,所有在await()上等待的线程被唤醒,然后继续向下执行,此时计数器又会被还原为创建时的值,然后可以继续再次使用。

public class CountDownLatchTest {
    public static void main(String[] args) throws Exception {
        // 发音[ˈsaɪklɪk], [ˈbæriə(r)]
        CyclicBarrier cyclicBarrier = new CyclicBarrier(2);
        Thread thread = new Thread() {
            @Override
            public void run() {
                try {
                    System.out.println("线程 1:我到了" + System.currentTimeMillis() / 1000);
                    cyclicBarrier.await();
                    System.out.println("线程 1:我要开吃了" + System.currentTimeMillis() / 1000);

                    System.out.println("线程 1:我上车了" + System.currentTimeMillis() / 1000);
                    cyclicBarrier.await();
                    System.out.println("线程 1:我出发了" + System.currentTimeMillis() / 1000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };
        thread.start();
        Thread thread2 = new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5000L);
                    System.out.println("线程 2:我到了" + System.currentTimeMillis() / 1000);
                    cyclicBarrier.await();
                    System.out.println("线程 2:我要开吃了" + System.currentTimeMillis() / 1000);

                    Thread.sleep(5000L);
                    System.out.println("线程 1:我上车了" + System.currentTimeMillis() / 1000);
                    cyclicBarrier.await();
                    System.out.println("线程 1:我出发了" + System.currentTimeMillis() / 1000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };
        thread2.start();



    }
}

输出

线程 1:我到了1566054035
线程 2:我到了1566054040
线程 2:我要开吃了1566054040
线程 1:我要开吃了1566054040
线程 1:我上车了1566054040
线程 1:我上车了1566054045
线程 1:我出发了1566054045
线程 1:我出发了1566054045

6. 自定义最后到达栅栏的线程的行为
CyclicBarrier的构造方法可以传入一个Runnable对象定义最后一个到达栅栏的线程需要执行的逻辑
public CyclicBarrier(int parties, Runnable barrierAction) {}
模拟最后一个到的人罚酒三杯

public class CountDownLatchTest {
    public static void main(String[] args) throws Exception {
        // 发音[ˈsaɪklɪk], [ˈbæriə(r)]
        CyclicBarrier cyclicBarrier = new CyclicBarrier(2, () -> {
            System.out.println(Thread.currentThread().getName() + "最后到的人罚酒三杯");
        });
        Thread thread = new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000L);
                    System.out.println("线程 1:我到了" + System.currentTimeMillis() / 1000);
                    cyclicBarrier.await();
                    System.out.println("线程 1:我要开吃了" + System.currentTimeMillis() / 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }
        };
        thread.start();
        Thread thread2 = new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5000L);
                    System.out.println("线程 2:我到了" + System.currentTimeMillis() / 1000);
                    cyclicBarrier.await();
                    System.out.println("线程 2:我要开吃了" + System.currentTimeMillis() / 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }
        };
        thread2.start();
    }
}

执行结果

线程 1:我到了1566117186
线程 2:我到了1566117190
Thread-1最后到的人罚酒三杯
线程 2:我要开吃了1566117190
线程 1:我要开吃了1566117190

6. 等待期间发生中断
等待期间发生中断,中断的线程会被唤醒,抛出InterruptedException继续向下执行
其它正在等待的线程抛出栅栏规则被破坏异常—BrokenBarrierException,
还未到达等待状态的线程在调用cyclicBarrier.await();时也会抛出BrokenBarrierException异常
例:线程1等的不耐烦了然后说我不等了,我先开吃了吃完我还有别的事。这种情况下由于规则被破坏其他正在等待的人也会抛出规则被破坏异常,然后说都有人开吃了那我也不等了。最后到的人一看大家都不遵守规则都吃上了,那他也就不管了先吃再说

public class CountDownLatchTest {
    public static void main(String[] args) throws Exception {
        // 发音[ˈsaɪklɪk], [ˈbæriə(r)]
        CyclicBarrier cyclicBarrier = new CyclicBarrier(2, () -> {
            System.out.println(Thread.currentThread().getName() + "最后到的人罚酒三杯");
        });
        Thread thread = new Thread() {
            @Override
            public void run() {
                try {
                    //Thread.sleep(1000L);
                    System.out.println("线程 1:我到了" + System.currentTimeMillis() / 1000);
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
                System.out.println("线程 1:我要开吃了" + System.currentTimeMillis() / 1000);
            }
        };
        thread.start();
        Thread thread2 = new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5000L);
                    System.out.println("线程 2:我到了" + System.currentTimeMillis() / 1000);
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
                System.out.println("线程 2:我要开吃了" + System.currentTimeMillis() / 1000);
            }
        };
        thread2.start();

        Thread.sleep(1500L);
        thread.interrupt();
    }
}

执行结果

线程 1:我到了1566117953
java.lang.InterruptedException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.reportInterruptAfterWait(AbstractQueuedSynchronizer.java:2014)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2048)
	at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:234)
	at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)
	at com.example.demo.king.thread.pool.CountDownLatchTest$1.run(CountDownLatchTest.java:17)
线程 1:我要开吃了1566117955
线程 2:我到了1566117958
java.util.concurrent.BrokenBarrierException
	at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:207)
	at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)
	at com.example.demo.king.thread.pool.CountDownLatchTest$2.run(CountDownLatchTest.java:33)
线程 2:我要开吃了1566117958

7. 循环栅栏await+等待时间
await方法可以添加等待时间参数,表示我只等xx长时间,如果人还没到齐我就破坏规则,自己先吃了。
cyclicBarrier.await(1,TimeUnit.SECONDS);
和上面例子的区别就是这里第一个线程抛出的是java.util.concurrent.TimeoutException而不是中断异常
更改上一个例子的代码await(),改成await(1,TimeUnit.SECONDS);
运行结果

线程 1:我到了1566118435
java.util.concurrent.TimeoutException
	at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:257)
	at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:435)
	at com.example.demo.king.thread.pool.CountDownLatchTest$1.run(CountDownLatchTest.java:17)
线程 1:我要开吃了1566118436
线程 2:我到了1566118440
java.util.concurrent.BrokenBarrierException
	at java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:207)
	at java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)
	at com.example.demo.king.thread.pool.CountDownLatchTest$2.run(CountDownLatchTest.java:35)
线程 2:我要开吃了1566118440

8. 栅栏重置
CyclicBarrier内部维护着一个递减的计数器,只有当所有线程都到达栅栏才会重置,可以重复使用;
但是如果栅栏规则被破坏(如上6、7的示例),则需要手动调用cyclicBarrier的reset()方法手动重置后才能重复使用

9. CountDownlatch和Cyclicbarrier的区别
CountDownlatch无法重复使用,Cyclicbarrier可以重复使用
CountDownlatch是让某几个线程等待另外的其它几个线程执行完毕后才执行,Cyclicbarrier是让n个线程互相等待然后一起往下执行

发布了52 篇原创文章 · 获赞 7 · 访问量 3815

猜你喜欢

转载自blog.csdn.net/maomaoqiukqq/article/details/99698806