JUC工具包之加法、减法计数器、信号量

1. CountDownLatch(减法计数器)

CountDownLatch 被人称作减法计数器,实际应用的时候,按照JDK文档的解释,可以有两种场景适用。

用法一是作为发令枪,一个线程拿着发令枪,下令使其他多个线程同时开始进行

用法二是观察所有线程的开始或结束时刻,比如公交车需要等所有乘客上车才能关门,或者是所有乘客下车才能关门,也就是当所有线程开始或结束了,才开始执行主线程的后续方法

补充:new CountDownLatch(N)、coutDown()、await() 必须配合起来使用,创建计数器对象的时候赋的值是多少,coutDown() 就必须执行多少次,否则计数器是没有清零的,计数器就不会停止,其他线程也无法唤醒,所以必须保证计数器清零,所以coutDown() 的调用次数必须大于构造函数的参数值。

(1) 用法一(发令枪)

使用步骤

new CountDownLatch(1); 在一个线程中(一般是主线程),调用该方法,创建一个值为1的减法计数器

CountDownLatch.await(); 在所有其他线程的run方法开始时,调用其方法,使线程阻塞

CountDownLatch.countDown(); 到一定时机后在主线程中下令,调用该方法(减法计数器减一),使计数器为0,所有线程同时进入执行状态

代码示例

public static void main(String[] args) {
        //1.创建一个线程发令枪(值为1的计数器)
        CountDownLatch startSingle = new CountDownLatch(1);

        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                System.out.println("我是"+Thread.currentThread().getName()+",正在等待下令");
                try {
                    startSingle.await();    //2.调用计数器await()方法使其等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("我是"+Thread.currentThread().getName()+",开始执行");
            },"线程"+(i+1)).start();
        }
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("=========");
        System.out.println("我是Main线程,马上开枪下令");
        System.out.println("=========");
        System.out.println("boom!开跑");
        startSingle.countDown(); //3. 调用countDown()方法,使计数器-1,进行下令
    }

在这里插入图片描述

(2) 用法二(记录所有线程的开始或结束时刻)

使用步骤

new CountDownLatch(N); 在一个线程中(一般是主线程),调用该方法,创建一个值为N的减法计数器

countDownLatch.countDown(); 在各个线程的执行方法中,调用该方法(减法计数器减一),使计数器减一,记录当前线程刚开始或已结束

countDownLatch.await(); 在主线程中调用方法,使其阻塞,等待接收计数器值为0

代码示例

static void allStartOrEnd(){
        CountDownLatch allStartSingle = new CountDownLatch(5); //1. 在主线程中创建一个值为N的减法计数器
        CountDownLatch allEndSingle = new CountDownLatch(5);

        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                allStartSingle.countDown(); //2. 在线程的执行任务中,使减法计数器-1
                System.out.println("我是"+Thread.currentThread().getName()+",开始执行");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("我是"+Thread.currentThread().getName()+",执行完毕,已结束");
                allEndSingle.countDown(); //减法计数器-1
            },"线程"+(i+1)).start();
        }

        try {
            allStartSingle.await(); //3. 使主线程阻塞,等待计数器为空
            try {
                Thread.sleep(50); // 主线程休眠一小会。避免打印消息比线程执行任务的打印消息快
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("我是主线程,观察到线程开始的计数器值为0,已知晓所有线程都已开始执行");
            System.out.println("==============");
            allEndSingle.await();
            System.out.println("我是主线程,观察到线程结束的计数器值为0,已知晓所有线程都已结束");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

在这里插入图片描述

2. CyclicBarrier(加法计数器)

加法计数器构造器可以传入两个参数
在这里插入图片描述
一般使用的时候,是将要定时(或者说是定值)完成的任务,放入加法计数器中,并赋予其放行任务的值,在开启其他线程使,调用cyclicBarrier.await();方法,使值+1,当值到达放行值的时候,就执行该任务

比如一辆车,除了司机最多坐4人,司机(也就是这里的加法计数器)要求坐满了才开车,每当一个人上车,人数就+1,到达4个,就开车

使用步骤

new CyclicBarrier(N,new Runnable()); 主线程中创建一个加法计数器

cyclicBarrier.await(); 一般是在其他线程执行时使计数器+1

代码示例

 public static void main(String[] args) {
        // 1. new CyclicBarrier(int N,new Runable()); 在主线程中创建一个加法计数器,并赋予其放行值和要执行的任务
        CyclicBarrier cyclicBarrier = new CyclicBarrier(5,new Runnable() { //为了明显,没有用lamdba表达式
            @Override
            public void run() {
                System.out.println("==========");
                System.out.println("加法计数器值到了5,放行该任务,且加法计数器的值重置为0");
            }
        });
        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                try {
                    System.out.println(Thread.currentThread().getName()+"正在执行,任务是使计数器值+1");
                    cyclicBarrier.await();  // 2. cyclicBarrier.await(); 使计数器+1 这里的await()方法是让加法计数器+1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            },"线程"+(i+1)).start();
        }
    }

3. Semaphore (信号量)

实际开发中主要使用它来完成限流操作,限制访问某些资源的线程数量

Semaphore底层其实是一个AQS(AbstractQueuedSynchronizer 抽象的队列式的同步器)

关于AQS的知识可以学习这两篇博客
非常详细
一般详细

Semaphore 的使用步骤:

  1. 初始化 new Semaphore(int n); //n为信号量的总数量
  2. 获取许可 thread.aquire(); //获取信号量的许可,同时信号量-1
  3. 释放 thread.release(int n); //一般写在finally中,用于释放当前线程的信号,n代表释放多少个,不写则默认是0

代码示例

public static void main(String[] args) {
        //初始化3个信号量
        Semaphore semaphore = new Semaphore(3);

        for (int i = 0; i < 10; i++) {
            new Thread((()->{
                try {
                    // 获取信号量的许可(信号量+1)
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName()+"抢到了车位,开始停车,获得一个信号量+++");
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                finally {
                    System.out.println(Thread.currentThread().getName()+"离开了车位,并释放当前信号量---");
                    //释放信号量
                    semaphore.release();
                }
            }),String.valueOf(i)).start();
        }
    }

在这里插入图片描述
注意点:指定释放的信号量数量时
比如只改动下一行代码
semaphore.release(); //释放一个信号量 ->semaphore.release(2); //释放两个信号量

当指定释放信号量的数量时,如果信号量释放到了初始化的最大值还在释放,那么此时信号量会扩容
在这里插入图片描述

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值