JUC三大常用工具类CountDownLatch、CyclicBarrier、Semaphore

21 篇文章 0 订阅

JUC三大常用工具类CountDownLatch、CyclicBarrier、Semaphore

CountDownLatch(减计数器)

概述

CountDownLatch位于 java.util.concurrent包下。

CountDownLatch是一个同步辅助类,允许一个或多个线程等待,一直到其他线程执行的操作完成后再执行。

CountDownLatch是通过一个计数器来实现的,计数器的初始值是线程的数量。每当有一个线程执行完毕后,通过countDown方法来让计数器的值-1,当计数器的值为0时,表示所有的线程都执行完毕,然后继续执行await方法之后的语句,即在锁上等待的线程就可以恢复工作了。
CountDownLatch中主要有两个方法:

  • CountDown:
    • 递减锁存器的计数,如果计数达到零,则释放所有等待的线程。
    • 如果当前计数大于零,则递减。如果新计数为零,则为线程调度目的重新启用所有等待线程。
    • 如果当前计数为零,则什么也不会发生。
      在这里插入图片描述
  • await
    • 使当前线程等待直到门锁倒计时为零,除非线程被中断。
    • 如果当前计数为零,则此方法立即返回。即await方法阻塞的线程会被唤醒,继续执行
    • 如果当前计数大于零,则当前线程出于线程调度目的而被禁用并处于休眠状态
      在这里插入图片描述

案例

简单的小例子:
一个寝室八个人要出去,需要等到1、2、3、4、5、6、7、8个人都出来,才可以锁上寝室门。即当计数器为0时,就可以执行await的方法了。

public class CountDownLatchDemo {
    public static void main(String[] args) {
        // 初始值8 有八个人需要出寝室门
        CountDownLatch countDownLatch = new CountDownLatch(8);
        for (int i = 1; i <= 8; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "出去啦");
                // 出去一个人计数器就减1
                countDownLatch.countDown();
            }, String.valueOf(i)).start();
        }
        try {
            countDownLatch.await(); // 阻塞等待计数器归零
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 阻塞的操作 : 计数器  num++
        System.out.println(Thread.currentThread().getName() + "====寝室人都已经出来了,关门向教室冲!!!====");
    }

}

结果
在这里插入图片描述

小结

CountDownLatch使用给定的计数进行初始化。 由于调用了countDown方法,每次-1, await方法会一直阻塞到当前计数达到零,然后释放所有等待线程,并且任何后续的await调用都会立即返回。 这是一种一次性现象——计数无法重置。 如果您需要重置计数的版本,请考虑使用CyclicBarrier 。
CountDownLatch一个有用属性是它不需要调用countDown线程在继续之前等待计数达到零,它只是阻止任何线程通过await,直到所有线程都可以通过。

CyclicBarrier(加法计数器)

概述

CyclicBarrier 看英文单词就可以看出大概就是循环阻塞的意思。所以还常称为循环栅栏。

CyclicBarrier 主要方法有:

public class CyclicBarrier {

    private int dowait(boolean timed, long nanos); // 供await方法调用 判断是否达到条件 可以往下执行吗
    
    //创建一个新的CyclicBarrier,它将在给定数量的参与方(线程)等待时触发,每执行一次CyclicBarrier就累加1,达到了parties,就会触发barrierAction的执行
    public CyclicBarrier(int parties, Runnable barrierAction) ;
    
    //创建一个新的CyclicBarrier ,参数就是目标障碍数,它将在给定数量的参与方(线程)等待时触发,每次执行 CyclicBarrier 一次障碍数会加一,如果达到了目标障碍数,才会执行 cyclicBarrier.await()之后的语句
    public CyclicBarrier(int parties) 
        
	//返回触发此障碍所需的参与方数量。
    public int getParties()
	
    //等待,直到所有各方都在此屏障上调用了await 。
	// 如果当前线程不是最后一个到达的线程,那么它会出于线程调度目的而被禁用并处于休眠状态.直到所有线程都调用了或者被中断亦或者发生异常中断退出
    public int await()
	
    // 基本同上 多了个等待时间 等待时间内所有线程没有完成,将会抛出一个超时异常
    public int await(long timeout, TimeUnit unit)

    //将障碍重置为其初始状态。 
    public void reset()

}

public CyclicBarrier(int parties):的构造方法第一个参数是目标障碍数,每次执行 CyclicBarrier 一次障碍数会加一,如果达到了目标障碍数,才会执行 cyclicBarrier.await()之后 的语句。可以将 CyclicBarrier 理解为加 1 操作。
public CyclicBarrier(int parties, Runnable barrierAction) :的构造方法第一个参数是目标障碍数,每次执行 CyclicBarrier 一次障碍数会加一,如果达到了目标障碍数,就会执行我们传入的Runnable;

案例

举一个抽奖的例子,累计抽奖200次则一定会得大奖。我们用代码模拟一下。

public class CyclicBarrierDemo {
    public static void main(String[] args) {
        // 第一个参数:目标障碍数  第二个参数:一个Runnable任务,当达到目标障碍数时,就会执行我们传入的Runnable
        // 当我们抽了201次的时候,就会执行这个任务。
        CyclicBarrier cyclicBarrier = new CyclicBarrier(201,()->{
            System.out.println("恭喜你,已经抽奖200次,幸运值已满,下次抽奖必中大奖!!!");
        });

        for (int i=1;i<200;i++){
            final int count=i;
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"抽奖一次");
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
        // 这行代码是重置计数
        cyclicBarrier.reset();

        // 这里是我又加了 一次循环, 可以看到最后结果中输出了两次 "恭喜你"
        for (int i=1;i<=200;i++){
            final int count=i;
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"抽奖一次");
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
    }
}

小结

CyclicBarrier和CountDownLatch其实非常相似,CyclicBarrier表示加法,CountDownLatch表示减法。
区别还是有的:

CyclicBarrier只能够唤醒一个任务,CountDownLatch可以唤起多个任务。
CyclicBarrier可以重置,重新使用,但是CountDownLatch的值等于0时,就不可重复用了。

Semaphore(信号灯)

概述

Semaphore:信号量通常用于限制可以访问某些(物理或逻辑)资源的线程数。

使用场景:

限制资源,如抢位置、限流等。

案例

不知道大家有没有过在网吧抢电脑打游戏的那种经历,小时候,平常便宜点的网吧都比较小,而且也比较少,特别多的人去,去晚了的人就只有站在那里看,等别人下机才能上网。

这次的例子就是:网吧有十台高配置打游戏的电脑,有20个小伙伴想要上网。

我们用代码来模拟一下:

public class SemaphoreDemo {
    public static void main(String[] args) {
        // 10台电脑
        Semaphore semaphore = new Semaphore(10);

        // 20 个小伙伴想要上网
        for (int i = 1; i <= 20; i++) {
            new Thread(() -> {
                try {
                    //等待获取许可证
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "抢到了电脑");
                    //抢到的小伙伴,迅速就开打啦 这里就模拟个时间哈,
                    TimeUnit.SECONDS.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    //打完几把游戏的小伙伴 女朋友来找了 溜啦溜啦 希望大家都有人陪伴
                    System.out.println("女朋友来找,"+Thread.currentThread().getName() + "离开了");
                    semaphore.release();//释放资源,离开了就要把电脑让给别人啦。
                }
            }, String.valueOf(i)).start();
        }
    }


}

小结

在获得一个项目之前,每个线程必须从信号量中获得一个许可,以保证一个项目可供使用。 当线程完成该项目时,它会返回到池中,并且将许可返回给信号量,允许另一个线程获取该项目。 请注意,调用acquire时不会持有同步锁,因为这会阻止项目返回到池中。 信号量封装了限制访问池所需的同步,与维护池本身一致性所需的任何同步分开。
初始化为 1 的信号量,并且使用时最多只有一个许可可用,可以用作互斥锁。 这通常被称为二进制信号量,因为它只有两种状态:一种许可可用,或零许可可用。 以这种方式使用时,二进制信号量具有属性(与许多java.util.concurrent.locks.Lock实现不同),即“锁”可以由所有者以外的线程释放(因为信号量没有所有权的概念)。 这在某些特定上下文中很有用,例如死锁恢复。
此类的构造函数可以选择接受公平参数。 当设置为 false 时,此类不保证线程获取许可的顺序。 当公平性设置为真时,信号量保证调用任何acquire方法的线程被选择以按照它们对这些方法的调用的处理顺序(先进先出;FIFO)获得许可。
通常,用于控制资源访问的信号量应初始化为公平的,以确保没有线程因访问资源而饿死。 当使用信号量进行其他类型的同步控制时,非公平排序的吞吐量优势通常超过公平性考虑。
内存一致性影响:在调用“释放”方法(如release()之前线程中的操作发生在另一个线程中成功的“获取”方法(如acquire()之后的操作之前。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
CountDownLatch 是一个计数器,它可以让一个或多个线程等待其他线程执行完毕后再继续执行。它的主要方法是 countDown() 和 await(),其 countDown() 用于计数减一,await() 用于等待计数器变为0。与 CountDownLatch 相比,CyclicBarrier 的主要区别在于它可以重复使用,而且所有线程必须同时到达栅栏处才能继续执行后续任务。CyclicBarrier 的重要方法是 await(),并且可以通过构造方法传入一个 Runnable,在所有线程都到达栅栏状态时优先执行该动作。CyclicBarrier 内部使用 ReentrantLock 和 Condition 实现等待和唤醒的功能,通过维护一个 count 变量来记录还有多少个线程没有到达栅栏处。 <span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [CountDownLatchCyclicBarrier](https://blog.csdn.net/weixin_44442186/article/details/123985119)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [JUC多线程:CountDownLatchCyclicBarrierSemaphore同步器原理总结](https://blog.csdn.net/a745233700/article/details/120688546)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

懒惰的coder

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值