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

Posted 若曦`

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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); //释放两个信号量

当指定释放信号量的数量时,如果信号量释放到了初始化的最大值还在释放,那么此时信号量会扩容

以上是关于JUC工具包之加法减法计数器信号量的主要内容,如果未能解决你的问题,请参考以下文章

Juc21_强大的三个工具类CountDownLatch 闭锁 CyclicBarrier Semaphore

Java多线程并发工具类-信号量Semaphore对象讲解

通过带有加法和减法计数器的视图控制器传递整数值[重复]

JUC三大常用辅助类

Excel 2007减法得数变成了5E-05怎么办?

编译器一日一练(DIY系列之连续加减法)