Java Semaphore/CountDownLatch/CyclicBarrier 深入解析(应用篇)

前言

线程并发系列文章:

Java 线程基础
Java “优雅”地中断线程
Java 线程状态
真正理解Java Volatile的妙用
Java ThreadLocal你之前了解的可能有误
Java Unsafe/CAS/LockSupport 应用与原理
Java 并发"锁"的本质(一步步实现锁)
Java Synchronized实现互斥之应用与源码初探
Java 对象头分析与使用(Synchronized相关)
Java Synchronized 偏向锁/轻量级锁/重量级锁的演变过程
Java Synchronized 重量级锁原理深入剖析上(互斥篇)
Java Synchronized 重量级锁原理深入剖析下(同步篇)
Java并发之 AQS 深入解析(上)
Java并发之 AQS 深入解析(下)
Java Thread.sleep/Thread.join/Thread.yield/Object.wait/Condition.await 详解
Java 并发之 ReentrantLock 深入分析(与Synchronized区别)
Java 并发之 ReentrantReadWriteLock 深入分析
Java Semaphore/CountDownLatch/CyclicBarrier 深入解析(原理篇)
Java Semaphore/CountDownLatch/CyclicBarrier 深入解析(应用篇)
最详细的图文解析Java各种锁(终极篇)

上篇文章分析了Semaphore/CountDownLatch/CyclicBarrier 实现原理,这次从应用的角度探索三者适用场合及其使用方式。
通过本篇文章,你将了解到:

1、场景引入
2、Semaphore 适用场景及使用方式
3、CountDownLatch 适用场景及使用方式
4、CyclicBarrier 适用场景及使用方式
5、三者适用场景总结

1、场景引入

网上很多文章在讲解这几个类的时候,分别举例讲解不同场景下的应用,这些例子本身没有紧密关联,不好横向对比。
因此,此处我们先预设整体场景。

参与主体

5位顾客到餐馆吃火锅,餐馆里有3位服务员。
桌上最多只能容下4盘菜。


Java Semaphore/CountDownLatch/CyclicBarrier 深入解析(应用篇)_第1张图片
image.png

活动内容

1、服务员上菜,顾客吃菜,服务员回收空盘子。
2、服务员涮1盘牛肚,需要烫5秒后顾客才能吃。
3、顾客自己上菜,等到每个人都上了一份菜之后才开吃。

2、Semaphore 适用场景及使用方式

场景说明

服务员开始上菜,当上了4盘菜之后,就不能再上了。需要将菜倒进锅里,然后将空盘子回收后才可以继续上菜。
这其实是典型共享有限资源场景,此处的资源即是:桌子上能放的菜盘子数。

Semaphore 使用

用Semaphore 模拟这种场景:

public class TestThread {

    //最大许可数量
    Semaphore semaphore = new Semaphore(4);

    //竞争共享资源的线程数
    final int threadCount = 5;

    class SemaphoreThread extends Thread{
        private Semaphore semaphore;
        public SemaphoreThread(Semaphore semaphore) {
            this.semaphore = semaphore;
        }

        @Override
        public void run() {
            try {
                System.out.println(Thread.currentThread().getName() + " 准备上菜 桌子上还可以放 " +
                        semaphore.availablePermits() + " 盘菜");
                semaphore.acquire();
                System.out.println(Thread.currentThread().getName() + " 已经上菜了");
                Thread.sleep(1000);

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            finally {
                semaphore.release();
                System.out.println(Thread.currentThread().getName() + " 回收空盘子 桌子上还可以放 " +
                        semaphore.availablePermits() + " 盘菜");
            }
        }
    }

    private void testSemaphore() {
        for (int i = 0 ;i < threadCount; i++) {
            Thread thread = new SemaphoreThread(semaphore);
            thread.setName("服务员" + (i + 1));
            thread.start();
        }
    }

    public static void main(String args[]) {
        TestThread testThread = new TestThread();
        testThread.testSemaphore();
    }
}

将每个服务员当作线程,将桌子上可放的盘子数量当作许可。
服务员上了菜之后,将空盘子回收。
打印如下:


Java Semaphore/CountDownLatch/CyclicBarrier 深入解析(应用篇)_第2张图片
image.png

这结果可能不是那么明显,是因为服务员太少了,上菜速度不够快,因此增加服务员数量,设置:

final int threadCount = 10;

再来看结果:


Java Semaphore/CountDownLatch/CyclicBarrier 深入解析(应用篇)_第3张图片
image.png

可以看出中途桌子上可放的盘子数量为0,说明有的服务员需要等待其他服务员回收空盘子。

Semaphore 许可的数量和线程数不存在必然的联系。

3、CountDownLatch 适用场景及使用方式

场景说明

服务员给顾客们涮牛肚,为了保证口味,该牛肚只需要烫5秒钟即可,在没烫好之前,顾客只能干巴巴等待,服务员烫好后会告知顾客可以吃了。

CountDownLatch 使用

Demo1

用CountDownLatch 模拟这种场景:

public class TestThread {
    //设置倒数计数
    CountDownLatch countDownLatch = new CountDownLatch(5);

    //等待计数的线程个数
    final int threadWaitCount = 5;
    //执行倒数计数的线程个数
    final int threadCLCount = 1;

    class WaitThread extends Thread{
        private CountDownLatch countDownLatch;
        public WaitThread(CountDownLatch countDownLatch) {
            this.countDownLatch = countDownLatch;
        }

        @Override
        public void run() {
            try {
                System.out.println(Thread.currentThread().getName() + " 等着吃牛肚 当前倒数计数: " + countDownLatch.getCount());
                //等待倒数结束
                countDownLatch.await();
                System.out.println(Thread.currentThread().getName() + " 开始吃牛肚");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    class CountDownThread extends Thread {
        private CountDownLatch countDownLatch;
        public CountDownThread(CountDownLatch countDownLatch) {
            this.countDownLatch = countDownLatch;
        }

        @Override
        public void run() {
            while (countDownLatch.getCount() > 0) {
                //倒数计数
                countDownLatch.countDown();
                System.out.println(Thread.currentThread().getName() + " 在倒数,当前计数:" + countDownLatch.getCount());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void testCountDownLatch() {
        for (int i = 0 ;i < threadWaitCount; i++) {
            Thread thread = new WaitThread(countDownLatch);
            thread.setName("顾客" + (i + 1));
            thread.start();
        }

        for (int i = 0; i < threadCLCount; i++) {
            Thread thread = new CountDownThread(countDownLatch);
            thread.setName("服务员" + (i + 1));
            thread.start();
        }
    }

    public static void main(String args[]) {
        TestThread testThread = new TestThread();
        testThread.testCountDownLatch();
    }
}

结果如下:

Java Semaphore/CountDownLatch/CyclicBarrier 深入解析(应用篇)_第4张图片
image.png

注:现在只是一个服务员在倒数计数,可以多个服务员倒数计数。
可以看出:

CountDownLatch 倒数计数和线程数不存在必然的联系。

Demo2

Demo1 针对的是一个或者多个线程倒数计数,然而一些时候我们需要每个线程只计数一次。
如:每个服务员只负责上一个菜,顾客需要等每个服务员都上了菜之后,才开始吃。

public class TestThread {

    //等待计数的线程个数
    final int threadWaitCount = 2;
    //执行倒数计数的线程个数
    final int threadCLCount = 3;

    //设置倒数计数
    CountDownLatch countDownLatch = new CountDownLatch(threadCLCount);

    class WaitThread extends Thread{
        private CountDownLatch countDownLatch;
        public WaitThread(CountDownLatch countDownLatch) {
            this.countDownLatch = countDownLatch;
        }

        @Override
        public void run() {
            try {
                System.out.println(Thread.currentThread().getName() + " 等待上菜 还差 " + countDownLatch.getCount() + " 个菜没上");
                //等待倒数结束
                countDownLatch.await();
                System.out.println(Thread.currentThread().getName() + " 菜上齐了,开吃");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    class CountDownThread extends Thread {
        private CountDownLatch countDownLatch;
        public CountDownThread(CountDownLatch countDownLatch) {
            this.countDownLatch = countDownLatch;
        }

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + " 开始上菜");
            countDownLatch.countDown();
            System.out.println(Thread.currentThread().getName() + " 完成上菜,还差 " + countDownLatch.getCount() + " 个菜没上");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private void testCountDownLatch() {
        for (int i = 0 ;i < threadWaitCount; i++) {
            Thread thread = new WaitThread(countDownLatch);
            thread.setName("顾客" + (i + 1));
            thread.start();
        }

        for (int i = 0; i < threadCLCount; i++) {
            Thread thread = new CountDownThread(countDownLatch);
            thread.setName("服务员" + (i + 1));
            thread.start();
        }
    }

    public static void main(String args[]) {
        TestThread testThread = new TestThread();
        testThread.testCountDownLatch();
    }
}

CountDownLatch 倒数计数设置为服务员(线程)人数,每个服务员上菜之后调用CountDownLatch.countDown()进行倒数计数。
结果如下:


Java Semaphore/CountDownLatch/CyclicBarrier 深入解析(应用篇)_第5张图片
image.png

4、CyclicBarrier 适用场景及使用方式

场景说明

还是以上菜为例,若是服务员太忙,那只能顾客自己上了,每位顾客上菜的时候看看菜齐了没(菜齐的标准是每位顾客都有一盘菜),若是发现菜上齐了就告知大伙儿可以开饭了。

CyclicBarrier 使用

用CyclicBarrier 模拟这种场景:

public class TestThread {

    //于栅栏处等待其它参与者
    final int threadWaitCount = 5;

    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            System.out.println("最后一个菜上了,上齐了");
            nextGeneration();
        }
    };

    //初始值为线程个数
    CyclicBarrier cyclicBarrier = new CyclicBarrier(threadWaitCount, runnable);

    class WaitThread extends Thread{
        private CyclicBarrier cyclicBarrier;
        public WaitThread(CyclicBarrier cyclicBarrier) {
            this.cyclicBarrier = cyclicBarrier;
        }

        @Override
        public void run() {
            try {
                System.out.println(Thread.currentThread().getName() + " 准备上菜,当前上了 " + cyclicBarrier.getNumberWaiting()
                + " 个菜");
                try {
                    cyclicBarrier.await();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " 菜上齐了,开吃");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private void testCyclicBarrier() {
        for (int i = 0 ;i < threadWaitCount; i++) {
            Thread thread = new WaitThread(cyclicBarrier);
            thread.setName("顾客" + (i + 1));
            thread.start();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private void nextGeneration() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("吃完了,开始下一轮上菜");
                testCyclicBarrier();
            }
        }).start();
    }

    public static void main(String args[]) {
        TestThread testThread = new TestThread();
        testThread.testCyclicBarrier();
    }
}

结果如下:


Java Semaphore/CountDownLatch/CyclicBarrier 深入解析(应用篇)_第6张图片
image.png

可以看出:

1、CyclicBarrier 初始的参与者个数就是线程的个数。
2、CyclicBarrier 可以重复利用。

5、三者适用场景总结

Java Semaphore/CountDownLatch/CyclicBarrier 深入解析(应用篇)_第7张图片
image.png

下篇将总结线程并发涉及到的锁的知识点,也是对并发系列文章的一次简短回顾。

本文基于jdk1.8。

您若喜欢,请点赞、关注,您的鼓励是我前进的动力

持续更新中,和我一起步步为营系统、深入学习Android/Java

1、Android各种Context的前世今生
2、Android DecorView 一窥全貌(上)
3、Android DecorView 一窥全貌(下)
4、Window/WindowManager 不可不知之事
5、View Measure/Layout/Draw 真明白了
6、Android事件分发全套服务
7、Android invalidate/postInvalidate/requestLayout 彻底厘清
8、Android Window 如何确定大小/onMeasure()多次执行原因
9、Android事件驱动Handler-Message-Looper解析
10、Android 键盘一招搞定
11、Android 各种坐标彻底明了
12、Android Activity/Window/View 的background
13、Android IPC 之Service 还可以这么理解
14、Android IPC 之Binder基础
15、Android IPC 之Binder应用
16、Android IPC 之AIDL应用(上)
17、Android IPC 之AIDL应用(下)
18、Android IPC 之Messenger 原理及应用
19、Android IPC 之获取服务(IBinder)
20、Android 存储基础
21、Android 10、11 存储完全适配(上)
22、Android 10、11 存储完全适配(下)
23、Java 并发系列不再疑惑

你可能感兴趣的:(Java Semaphore/CountDownLatch/CyclicBarrier 深入解析(应用篇))