多线程并发工具类 CountDownLatch,CyclicBarrier,Semaphore

目录

1.什么是控制并发流程?

2.常用的控制并发流程工具类概述

2.1 CountDownLatch(倒计时门闩)

 流程:

主要方法:

两个典型用法:

2.2 Semaphore(信号量)

概述:

使用流程:

主要方法介绍:

代码演示:

注意点:

2.3 CyclicBarrier(循环栅栏)

概述:

代码演示:

可重入性演示:

和CountDownLatch区别:


1.什么是控制并发流程?

  • 控制并发流程的工具类,作用就是帮助我们更容易得让线程之间合作
  • 让线程之间相互配合,来满足业务逻辑
  • 比如让线程A等待线程B执行完毕后再执行等合作策略

2.常用的控制并发流程工具类概述

作用说明
Semaphore信号量,可以通过控制‘许可证’的数量,来保证线程之间的配合线程只有拿到许可证后,才能继续运行,相比其他同步器,更灵活
CyclicBarrier线程会等待,直到足够多的线程到达了事先规定的数目。一旦达到触发条件,就可以进行下一步动作适用于线程之互相等待处理结果就绪的场景
CountDownLatch和CyclicBarrier类似,数量递减到0时,触发动作Java7加入的

2.1 CountDownLatch(倒计时门闩)

 流程:

会进行倒数操作,倒数结束之前一直处于等待状态,直到倒计时结束了,此线程才继续工作

主要方法:

CountDownLatch(int count):仅有一个构造函数,参数count为需要倒数的数值

await():调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行

countDown():将count值减1,直到为0时,等待的线程会被唤起

两个典型用法:

用法一:一个线程等待多个线程都执行完毕,再继续自己的工作。

示例:

public class CountDownLatchDemo1 {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(5);
        ExecutorService service = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
            int no = i;
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    try {
                        // 随机睡眠
                        Thread.sleep((long) (Math.random() * 1000));
                        System.out.println("No." + no + "检查完了。。。");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        // 每个线程执行完成后都会进行等待
                        countDownLatch.countDown();
                    }
                }
            };
            service.submit(runnable);
        }
        System.out.println("等待5个人检查完。。。");
        countDownLatch.await();
        System.out.println("所有人都检查完了,进入下一个环节");

    }
}

执行结果:
等待5个人检查完。。。
No.2检查完了。。。
No.1检查完了。。。
No.0检查完了。。。
No.4检查完了。。。
No.3检查完了。。。
所有人都检查完了,进入下一个环节

用法二:多个线程等待某一个线程的信号,同时开始执行

/**
 * 描述:模拟100米跑步,5名选手都准备好了,等裁判号令,所有人同时开始跑步
 */
class CountDownLatchDemo2 {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch begin = new CountDownLatch(1);
        ExecutorService service = Executors.newFixedThreadPool(5);
        for (int i = 1; i <=5 ; i++) {
            final int no = i;
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    System.out.println("NO." + no + "准备完毕,等待发令枪");
                    try {
                        begin.await();
                        System.out.println("NO." + no + "开始跑步了");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            service.submit(runnable);
        }
        // 裁判员检查发令枪
        Thread.sleep(5000);
        System.out.println("枪响了,比赛开始");
        begin.countDown();
    }
}

执行结果:
NO.1准备完毕,等待发令枪
NO.2准备完毕,等待发令枪
NO.3准备完毕,等待发令枪
NO.4准备完毕,等待发令枪
NO.5准备完毕,等待发令枪
枪响了,比赛开始
NO.1开始跑步了
NO.2开始跑步了
NO.4开始跑步了
NO.3开始跑步了
NO.5开始跑步了

结合用法一和用法二:

/**
 * 描述:模拟100米跑步,5名选手都准备好了,等裁判号令,所有人同时开始跑步.
 *      当所有人都到终点后比赛结束
 */
class CountDownLatchDemo1And2 {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch begin = new CountDownLatch(1);
        CountDownLatch end = new CountDownLatch(5);
        ExecutorService service = Executors.newFixedThreadPool(5);
        for (int i = 1; i <=5 ; i++) {
            final int no = i;
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    System.out.println("NO." + no + "准备完毕,等待发令枪");
                    try {
                        begin.await();
                        System.out.println("NO." + no + "开始跑步了");
                        // 模拟随机跑步时长
                        Thread.sleep((long) (Math.random() * 10000));
                        System.out.println("NO." + no + "跑到终点了");

                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }finally {
                        end.countDown();
                    }
                }
            };
            service.submit(runnable);
        }
        // 裁判员检查发令枪
        Thread.sleep(5000);
        System.out.println("枪响了,比赛开始");
        begin.countDown();
        end.await();
        System.out.println("所有人到达终点,比赛结束");
    }
}
执行结果:
NO.1准备完毕,等待发令枪
NO.2准备完毕,等待发令枪
NO.3准备完毕,等待发令枪
NO.4准备完毕,等待发令枪
NO.5准备完毕,等待发令枪
枪响了,比赛开始
NO.1开始跑步了
NO.3开始跑步了
NO.5开始跑步了
NO.4开始跑步了
NO.2开始跑步了
NO.1跑到终点了
NO.4跑到终点了
NO.3跑到终点了
NO.2跑到终点了
NO.5跑到终点了
所有人到达终点,比赛结束

注意:

  • 扩展用法:多个线程等多个线程完成执行后,再同时执行
  • CountDownLatch不可重用

2.2 Semaphore(信号量)

概述:

  • 用来限制或管理数量有限的资源的使用情况
  • 信号量的作用是维护一个"许可证"的计数,线程可以"获取"许可证,那信号量剩余的许可证就减一,线程也可以"释放"一个许可证, 那信号量剩余的许可证就加一,当信号量所拥有的许可证数量为0,那么下一个还想要获取许可证的线程,就需要等待,直到有另外的线程释放了许可证

使用流程:

  1. 初始化Semaphore并指定许可证的数量
  2. 在需要被使用的代码前加acquire()或者acquireUninterruptibly()方法
  3. 任务执行结束后,调用release()来释放许可证

主要方法介绍:

  • Semaphore(int permits, boolean fair) 构造函数,这里可以设置是否使用公平策略;如果传入true,那么会把之前等待的线程放到FIFO的队列里,以便于当有了新的许可证,可以分发给之前等待了最长时间的线程
  • acquire();获取许可证,可以响应中断 acquireUninterruptibly();不允许被中断 release:释放许可证
  • tryAcquire():看看现在有没有空闲许可证,如果有的话就获取,如果没有的话也没有关系,我不必陷入阻塞,我可以去做别的事过一会再来查看许可证的空闲情
  • tryAcquire(timeout):和tryAcquire()一样,但是多了一个超时时间,比如"3秒内获取不到许可证,我就去做别的事"

代码演示:

/**
 * 描述:一次最多拿到3个许可证
 */
class SemaphoreDemo{
   static Semaphore semaphore = new Semaphore(3,true);

    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(50);
        for (int i = 0; i < 100; i++) {
            service.submit(new Task());
        }
        service.shutdown();
    }

    static class Task implements Runnable{

        @Override
        public void run() {
            try {
                semaphore.acquire();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"拿到了许可证");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"释放了许可证");
            semaphore.release();
        }
    }
}
执行结果:
pool-1-thread-1拿到了许可证
pool-1-thread-2拿到了许可证
pool-1-thread-3拿到了许可证
pool-1-thread-1释放了许可证
pool-1-thread-2释放了许可证
pool-1-thread-3释放了许可证
pool-1-thread-4拿到了许可证
pool-1-thread-5拿到了许可证
pool-1-thread-6拿到了许可证
pool-1-thread-5释放了许可证
pool-1-thread-4释放了许可证
pool-1-thread-6释放了许可证

注意点:

  • 获取和释放许可证数量必须一致,否则比如每次都获取2个但是只释放1个甚至不释放,随着时间的推移,到最后许可证数量不够用,会导致程序卡死(信号量类不会对是否和获取的数量做规定,但是这是我们的编程规范,否则容易出错)
  • 注意在初始化Semaphore的时候设置公平性,一般设置为true会更合理
  • 并不是必须由获取许可证的线程释放那个许可证,事实上,获取和释放许可证对线程并无要求,也许是A获取了,然后B释放,只要逻辑合理即可

2.3 CyclicBarrier(循环栅栏)

概述:

  • CyclicBarrier循环栅栏和CountDownLatch很相似,都能阻塞一组线程
  • 当有大量线程相互配合,分别计算不同任务,并且需要最后统一汇总的时候,我们可以使用CyclicBarrier。CyclicBarrier可以构造一个集结点,当某一个线程执行完毕,它就会到集结点等待,直到所有线程都到了集结点,那么该栅栏就会被撤销,所有线程再统一出发,继续执行剩下的任务

代码演示:


/**
 * 描述:演示CyclicBarrier,5个人相约出行,全部到场再出发
 */
class CyclicBarrierDemo{
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(5, new Runnable() {
            @Override
            public void run() {
                System.out.println("所有人都到场了,大家统一出发");
            }
        });
        for (int i = 0; i < 5; i++) {
            new Thread(new Task(i,cyclicBarrier)).start();
        }
    }

    static class Task implements Runnable{

        private int id;
        private CyclicBarrier cyclicBarrier;

        public Task(int id){
            this.id = id;
        }

        public Task(int id, CyclicBarrier cyclicBarrier) {
            this.id = id;
            this.cyclicBarrier = cyclicBarrier;
        }

        @Override
        public void run() {
            System.out.println("线程"+id+"现在前往集合地点");
            try {
                Thread.sleep((long) (Math.random()*1000));
                System.out.println("线程"+id+"到了集合地点,开始等待其他人到达");
                cyclicBarrier.await();
            System.out.println("线程"+id+"出发了");
            }catch (InterruptedException e){
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }
}
执行结果:
线程0现在前往集合地点
线程1现在前往集合地点
线程2现在前往集合地点
线程3现在前往集合地点
线程4现在前往集合地点
线程0到了集合地点,开始等待其他人到达
线程3到了集合地点,开始等待其他人到达
线程4到了集合地点,开始等待其他人到达
线程2到了集合地点,开始等待其他人到达
线程1到了集合地点,开始等待其他人到达
所有人都到场了,大家统一出发
线程1出发了
线程0出发了
线程4出发了
线程2出发了
线程3出发了

可重入性演示:

for (int i = 0; i < 5; i++) 这行5改成10

执行结果:
线程0现在前往集合地点
线程1现在前往集合地点
线程2现在前往集合地点
线程3现在前往集合地点
线程4现在前往集合地点
线程5现在前往集合地点
线程6现在前往集合地点
线程7现在前往集合地点
线程8现在前往集合地点
线程9现在前往集合地点
线程7到了集合地点,开始等待其他人到达
线程3到了集合地点,开始等待其他人到达
线程4到了集合地点,开始等待其他人到达
线程0到了集合地点,开始等待其他人到达
线程2到了集合地点,开始等待其他人到达
所有人都到场了,大家统一出发
线程2出发了
线程7出发了
线程3出发了
线程0出发了
线程4出发了
线程6到了集合地点,开始等待其他人到达
线程1到了集合地点,开始等待其他人到达
线程9到了集合地点,开始等待其他人到达
线程5到了集合地点,开始等待其他人到达
线程8到了集合地点,开始等待其他人到达
所有人都到场了,大家统一出发
线程8出发了
线程6出发了
线程1出发了
线程9出发了
线程5出发了

和CountDownLatch区别:

  • 作用不同:CyclicBarrier需要等固定数量的线程都达到了栅栏位置才能继续执行,而CountDownLatch值需要等待数字到0,也就是说,CountDownLatch用于事件,而CyclicBarrier是用于线程的
  • 可重用性不同:CountDownLatch在倒数到0并触发门闩打开后,就不能再次使用了,除非新建新的实例,而CyclicBarrier可以重复使用
  • 8
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值