• 【JUC】六、辅助类



    本篇整理JUC的几个同步辅助类:

    • 减少计数:CountDownLatch
    • 循环栅栏:CyclicBarrier
    • 信号灯:Semaphore

    1、CountDownLatch减少计数

    案例:6个同学陆续离开教室后,班长才可以锁门

    先不使用CountDownLatch,看下效果:

    public class CountDownDemo {
    
        public static void main(String[] args) {
           
            for (int i = 1; i < 7; i++) {
                new Thread(() -> {
                    System.out.println(Thread.currentThread().getName() + "号同学离开了教室");
                },String.valueOf(i)).start();
            }
            System.out.println(Thread.currentThread().getName() + " 班长锁门走人了");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    运行:这里循环执行6次start,创建6个就绪状态的线程后,main线程继续向下执行,而这6个新线程还要抢夺CPU时间片、执行,因此可能出现其他线程未执行结束,main线程(班长)就执行结束的情况。(先main线程中start创建线程,再抢时间片,抢到后才执行run方法,不要迷)

    在这里插入图片描述

    改进,引入CountDownLatch:

    public class CountDownDemo {
    
        public static void main(String[] args) throws InterruptedException {
            //创建CountDownLatch对象,设置初始值
            CountDownLatch countDownLatch = new CountDownLatch(6);
            for (int i = 1; i < 7; i++) {
                new Thread(() -> {
                    System.out.println(Thread.currentThread().getName() + "号同学离开了教室");
                    //计数减一(因为到这儿,新线程的run方法也执行完了)
                    countDownLatch.countDown();
                },String.valueOf(i)).start();
            }
            //计数器未成0前,让当前线程(main线程)挂起
            countDownLatch.await();
            System.out.println(Thread.currentThread().getName() + " 班长锁门走人了");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在这里插入图片描述

    CountDownLatch类的常用方法

    • 构造器方法,传入初始的计数
    CountDownLatch(int count)
    
    • 1
    • 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断
    void await()
    
    • 1
    • 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间
    boolean await(long timeout, TimeUnit unit)
    
    • 1
    • 锁存器计数减一
    void countDown()
    
    • 1
    • 返回当前计数
    long getCount()
    
    • 1

    总结

    • 定义计数器初始值
    • 每次相关操作后,让计数器减一
    • 计数器的值没变成0 ,当前线程就一直await,等计数器变成0后,await后面的代码才会继续执行

    2、CyclicBarrier循环栅栏

    作用

    让一组线程互相等待,直到达到某个公共的屏障点。且可以在达到这个公共屏障点后(在一组线程中的最后一个线程到达之后,但在释放所有线程之前)执行一个Runnable。注意不是新开一个线程去执行,而是由最后一个进入 barrier 的线程执行。

    案例:集齐7颗龙珠才可以召唤神龙

    public class CyclicDemo {
    
        //设置固定值
        private static final int NUMBER = 7;
    
        public static void main(String[] args) {
            //创建CyclicBarrier
            CyclicBarrier cyclicBarrier = new CyclicBarrier(NUMBER, () -> {
                System.out.println("集齐7颗龙珠,召唤神龙成功!");
            });
            //集齐七颗龙珠的过程
            for (int i = 1; i <= 7 ; i++) {
                new Thread(() -> {
                    try {
                        System.out.println(Thread.currentThread().getName() + "星龙珠被收集到了");
                        cyclicBarrier.await();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                },String.valueOf(i)).start();
            }
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    运行:

    在这里插入图片描述

    修改参数,让for循环跑6次(即只创建6个线程去调用await,刻意不让其到达number),而number仍然为7,此时运行,程序一直等待,最后exit -1

    在这里插入图片描述
    CyclicBarrier类的常用方法

    • 构造方法,传入参与线程的数量,和最后要执行的操作
    CyclicBarrier(int parties, Runnable barrierAction)
    
    • 1
    • 在所有参与线程未调用CyclicBarrier对象的await方法之前,将一直等待,也就是上面演示的达到不了那个number。这些参与的线程,在其最后调用await,就相当于在说我已在当前屏障出等待。而若有参与线程不调用await,就像春游发车前少个人一样,只能整个线程组都等着。
    int await()
    
    • 1
    • 同上,未到屏障点前,所有调用了await的线程(参与线程)就一直等待,除非到了超时时间
    await(long timeout, TimeUnit unit)
    
    • 1
    • 返回当前已到达屏障点的线程的数量(春游实际到上车点的人数)
    int getNumberWaiting()
    
    • 1
    • 返回传入的屏障点(春游报名人数)
    int getParties()
    
    • 1

    总结

    Barrier,屏障,参与的线程调一次await,即说明该线程已在屏障出等待,屏障点+1,达到这个屏障点,线程组的所有线程才继续往下执行,否则之前调用了await方法的线程就一直处于await等待状态,除非到了指定的超时时间

    3、Semaphore信号灯

    作用

    维护一个信号量,这个信号量里有多个许可,拿到许可就执行,没拿到就等着。有点像对象锁了,而和对象锁不同的是,一个对象,一把对象锁,但Semaphore的这个锁(许可)的数量是你自己传入的。

    案例:6辆汽车,停三个停车位

    车停在车位A,其他车就不能再停在A位了,这就是获取一个许可。车开出去,就是释放一个许可

    acquire获取许可证后,其他线程只能一直等待,阻塞状态

    public class SemaphoreDemo {
    
        public static void main(String[] args) {
            //创建Semaphore,设置许可数量,三个车位,对应三个许可证
            Semaphore semaphore = new Semaphore(3);
            //模拟6辆汽车
            for (int i = 1; i <= 6; i++) {
                new Thread(() -> {
                    try {
                        //抢占许可证
                        semaphore.acquire();
                        System.out.println(Thread.currentThread().getName() + "抢到了车位");
                        //设置一个5s以内的随机时间,模拟停车
                        TimeUnit.SECONDS.sleep(new Random().nextInt(5));
                        System.out.println(Thread.currentThread().getName() + "=====> 离开了车位");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        //释放许可
                        semaphore.release();
                    }
                },String.valueOf(i)).start();
    
    
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    运行:

    在这里插入图片描述

    Semaphore类的常用方法

    • 构造方法,传入一个数字,如果把Semaphore类看成我们前面并发编程步骤里的资源类,那这个数字就是设置有几把对象锁
    Semaphore(int permits)
    
    • 1
    • 构造方法重载,可传入一个Boolean,表示是否公平的给许可证,设置false时,是允许新启动的线程插队的,设置为true,则按照先进先出的方式来在等待的线程队列中选择线程,发放许可
    Semaphore(int permits, boolean fair)
    
    • 1
    • 获取许可
    void acquire()
    
    • 1
    • 释放许可
    void release()
    
    • 1

    在这里插入图片描述

    总结

    个人理解就是,如果把Semaphore类看成我们前面并发编程步骤里的资源类,那它特殊的地方就是,这个资源类的一个对象有几把对象锁,是我们可以自己在构造方法里设置的,而普通的自定义资源类,想要n把对象锁,就得new上n个对象。


    最后,API文档地址:https://tool.oschina.net/apidocs/apidoc?api=jdk-zh

  • 相关阅读:
    Starknet的去中心化路线图
    分片上传与断点续传
    iOS 让界面元素的文字随着语言的更改而变化——本地化文字跟随
    unity打包工具
    JAVA计算机毕业设计智能化车辆管理综合信息平台Mybatis+源码+数据库+lw文档+系统+调试部署
    电商卖家保障数据隐私和安全用什么安全的浏览器?
    【云原生之k8s】kubernetes核心组件
    【一起来学C++】————(11)STL之vector容器容器
    线程池——futuretask、CompletionService、CompletableFuture
    字符函数和字符串函数2(C语言进阶)
  • 原文地址:https://blog.csdn.net/llg___/article/details/134407223