多线程进阶学习11------CountDownLatch、CyclicBarrier、Semaphore详解

CountDownLatch

①. CountDownLatch主要有两个方法,当一个或多个线程调用await方法时,这些线程会阻塞

②. 其它线程调用countDown方法会将计数器减1(调用countDown方法的线程不会阻塞)

③. 计数器的值变为0时,因await方法阻塞的线程会被唤醒,继续执行

public static void main(String[] args) throws Exception {
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i = 1; i <= 6; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "\t");
                countDownLatch.countDown();
            }, i + "").start();
        }
        countDownLatch.await();
        System.out.println(Thread.currentThread().getName() + "\t班长关门走人,main线程是班长");
    }

比如说CountDownLatch你就可以回答:前面提到了CountDownLatch也是基于AQS实现的,它的实现机制很简单,

  • 当我们在构建CountDownLatch对象时,传入的值其实就会赋值给 AQS 的关键变量state
  • 执行CountDownLatch的countDown方法时,其实就是利用CAS 将state 减一
  • 执行await方法时,其实就是判断state是否为0,不为0则加入到阻塞队列中,将该线程阻塞掉(除了头结点),因为头节点会一直自旋等待state为0,当state为0时,头节点把剩余的在队列中阻塞的节点也一并唤醒。

再简单总结下:CountDownlatch基于AQS实现,会将构造CountDownLatch的入参传递至state,countDown()就是在利用CAS将state减-1,
await()实际就是让头节点一直在等待state为0时,释放所有等待的线程

CyclicBarrier

CyclicBarrier原理

1、它没有像CountDownLatch和ReentrantLock使用AQS的state变量,而是使用CyclicBarrier内部维护的内部维护count变量

2、同时CyclicBarrier借助ReentrantLock加上Condition实现等待唤醒的功能

parties变量和condition队列

在构建CyclicBarrier时,传入的值是parties变量,同时也会赋值给CyclicBarrier内部维护count变量(这是可以复用的关键)

//parties表示屏障拦截的线程数量,当屏障撤销时,先执行barrierAction,然后在释放所有线程
public CyclicBarrier(int parties, Runnable barrierAction)
//barrierAction默认为null
public CyclicBarrier(int parties)

每次调用await时,会将count -1 ,操作count值是直接使用ReentrantLock来保证线程安全性

如果count不为0,则添加则condition队列中

如果count等于0时,则把节点从condition队列添加至AQS的队列中进行全部唤醒,并且将parties的值重新赋值为count的值(实现复用)
在这里插入图片描述

再简单总结下:CyclicBarrier则利用ReentrantLock和Condition,自身维护了count和parties变量。 每次调用await将count-1,并将线程加入到condition队列上。等到count为0时,则将condition队列的节点移交至AQS队列,并全部释放。

阻塞任务线程而非主线程
CountDownLatch和CyclicBarrier都是线程同步的工具类。可以发现这两者的等待主体是不一样的。

  • CountDownLatch调用await()通常是主线程/调用线程
  • CyclicBarrier调用await()是在任务线程调用的,所以,CyclicBarrier中的阻塞的是任务的线程,而主线程是不受影响的。

比如集齐7颗龙珠就能召唤神龙。

  //集齐7颗龙珠就能召唤神龙
public class CyclicBarrierDemo {
    public static void main(String[] args) {
        // public CyclicBarrier(int parties, Runnable barrierAction) {}
        CyclicBarrier cyclicBarrier=new CyclicBarrier(7,()->{
            System.out.println("召唤龙珠");
        });
        for (int i = 1; i <=7; i++) {
            final int temp=i;
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"\t收集到了第"+temp+"颗龙珠");
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }

    }
}

共同抵达时的执行操作

屏障的抵达操作:每个线程执行时,都会碰到一个屏障,直到所有线程执行结束,然后屏障便会打开,使所有线程继续往下执行。

  • CyclicBarrier支持一个可选的Runnable barrierAction命令,在一组线程中的最后一个线程到达之后,但在释放所有线程之前运行一次。
  • 若在继续所有参与线程之前更新共享状态,此屏障操作很有用。

如果一个寝室四个人约好了去球场打球,由于四个人准备工作不同,所以约好在楼下集合,并且四个人集合好之后一起出发去球场。

private static final ThreadPoolExecutor threadPool =
            new ThreadPoolExecutor(4, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());

    //当拦截线程数达到4时,便优先执行barrierAction,然后再执行被拦截的线程。
    private static final CyclicBarrier cb = new CyclicBarrier(4, () -> System.out.println("寝室四兄弟一起出发去球场"));

    private static class MyThread extends Thread {
        private String name;

        public MyThread(String name) {
            this.name = name;
        }

        @Override
        public void run() {
            System.out.println(name + "开始从宿舍出发");
            try {
                cb.await();
                //线程的具体业务操作
                TimeUnit.SECONDS.sleep(1);
                System.out.println(name + "从楼底下出发");
                TimeUnit.SECONDS.sleep(1);
                System.out.println(name + "到达操场");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        String[] str = {"李明", "王强", "刘凯", "赵杰"};
        for (int i = 0; i < 4; i++) {
            threadPool.execute(new MyThread(str[i]));
        }
        try {
            Thread.sleep(4000);
            System.out.println("四个人一起到达球场,现在开始打球");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

屏障复用

CyclicBarrier是可循环利用的屏障,顾名思义,这个名字也将这个类的特点给明确地表示出来了。可重复利用,说明该类创建的对象可以复用;CyclicBarrier是一个同步工具类,它允许一组线程互相等待,直到到达某个公共屏障点。与CountDownLatch不同的是该barrier在释放等待线程后可以重用,所以称它为循环(Cyclic)的屏障(Barrier)。

现在对CyclicBarrier进行复用…又来了一拨人,看看愿不愿意一起打:

 private static final ThreadPoolExecutor threadPool = new ThreadPoolExecutor(4, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
    //当拦截线程数达到4时,便优先执行barrierAction,然后再执行被拦截的线程。
    private static final CyclicBarrier cb = new CyclicBarrier(4, () -> System.out.println("寝室四兄弟一起出发去球场"));

    private static class MyThread extends Thread {
        private String name;
        public MyThread(String name) {
            this.name = name;
        }

        @Override
        public void run() {
            System.out.println(name + "开始从宿舍出发");
            try {
                cb.await();
                TimeUnit.SECONDS.sleep(1);
                System.out.println(name + "从楼底下出发");
                TimeUnit.SECONDS.sleep(1);
                System.out.println(name + "到达操场");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        String[] str = {"李明", "王强", "刘凯", "赵杰"};
        for (int i = 0; i < 4; i++) {
            threadPool.execute(new MyThread(str[i]));
        }
        try {
            Thread.sleep(4000);
            System.out.println("四个人一起到达球场,现在开始打球");
            System.out.println();
            System.out.println("现在对CyclicBarrier进行复用.....");
            System.out.println("又来了一拨人,看看愿不愿意一起打:");

        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        String[] str1= {"王二","洪光","雷兵","赵三"};
        for (int i = 0; i < 4; i++) {
            threadPool.execute(new MyThread(str1[i]));
        }
        try {
            Thread.sleep(4000);
            System.out.println("四个人一起到达球场,表示愿意一起打球,现在八个人开始打球");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

Semaphore

Semaphore 是什么?Semaphore 通常我们叫它信号量, 可以用来控制同时访问特定资源的线程数量,通过协调各个线程,以保证合理的使用资源。

使用场景:

通常用于那些资源有明确访问数量限制的场景,常用于限流 。

  • 比如:数据库连接池,同时进行连接的线程有数量限制,连接不能超过一定的数量,当连接达到了限制数量后,后面的线程只能排队等前面的线程释放了数据库连接才能获得数据库连接。
  • 比如:停车场场景,车位数量有限,同时只能容纳多少台车,车位满了之后只有等里面的车离开停车场外面的车才可以进入。可以把它简单的理解成我们停车场入口立着的那个显示屏,每有一辆车进入停车场显示屏就会显示剩余车位减1,每有一辆车从停车场出去,显示屏上显示的剩余车辆就会加1,当显示屏上的剩余车位为0时,停车场入口的栏杆就不会再打开,车辆就无法进入停车场了,直到有一辆车从停车场出去为止。
  • 比如,接口限流 ,应用限流 ,商品限流…

Semaphore常用方法说明

acquire()  
获取一个令牌,在获取到令牌、或者被其他线程调用中断之前线程一直处于阻塞状态。

tryAcquire(long timeout, TimeUnit unit)
尝试在指定时间内获得令牌,返回获取令牌成功或失败,不阻塞线程。

release()
释放一个令牌,唤醒一个获取令牌不成功的阻塞线程。

hasQueuedThreads()
等待队列里是否还存在等待线程。

getQueueLength()
获取等待队列里阻塞的线程数。

drainPermits()
清空令牌把可用令牌数置为0,返回清空令牌的数量。

availablePermits()
返回可用的令牌数量。

用semaphore 实现停车场提示牌功能

每个停车场入口都有一个提示牌,上面显示着停车场的剩余车位还有多少,当剩余车位为0时,不允许车辆进入停车场,直到停车场里面有车离开停车场,这时提示牌上会显示新的剩余车位数。

业务场景 :

  • 停车场容纳总停车量10。
  • 当一辆车进入停车场后,显示牌的剩余车位数响应的减1.
  • 每有一辆车驶出停车场后,显示牌的剩余车位数响应的加1。
  • 停车场剩余车位不足时,车辆只能在外面等待。
public class TestCar {

    //停车场同时容纳的车辆10
    private  static  Semaphore semaphore=new Semaphore(10);

    public static void main(String[] args) {

        //模拟100辆车进入停车场
        for(int i=0;i<100;i++){

            Thread thread=new Thread(new Runnable() {
                public void run() {
                    try {
                        System.out.println("===="+Thread.currentThread().getName()+"来到停车场");
                        if(semaphore.availablePermits()==0){
                            System.out.println("车位不足,请耐心等待");
                        }
                        semaphore.acquire();//获取令牌尝试进入停车场
                        System.out.println(Thread.currentThread().getName()+"成功进入停车场");
                        Thread.sleep(new Random().nextInt(10000));//模拟车辆在停车场停留的时间
                        System.out.println(Thread.currentThread().getName()+"驶出停车场");
                        semaphore.release();//释放令牌,腾出停车场车位
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },i+"号车");

            thread.start();

        }

    }
}

用semaphore 实现防止商品超卖

package com.limiting.semaphore;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 * 秒杀防止商品超卖现象
 */
public class SemaphoreCommodity {

    //商品池
  private   Map<String, Semaphore> map=new ConcurrentHashMap<>();

    //初始化商品池
    public SemaphoreCommodity() {
        //手机10部
        map.put("phone",new Semaphore(10));
        //电脑4台
        map.put("computer",new Semaphore(4));
    }

    /**
     *
     * @param name 商品名称
     * @return 购买是否成功
     */
    public boolean getbuy(String name) throws Exception {

        Semaphore semaphore = map.get(name);
        while (true) {
            int availablePermit = semaphore.availablePermits();
            if (availablePermit==0) {
                //商品售空
                return  false;
            }
            boolean b = semaphore.tryAcquire(1, TimeUnit.SECONDS);
            if (b) {
                System.out.println("抢到商品了");
                ///处理逻辑
                return  true;
            }

        }

    }

    public static void main(String[] args) throws Exception {
        SemaphoreCommodity semaphoreCommodity=new SemaphoreCommodity();
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                try {
                    System.out.println(semaphoreCommodity.getbuy("computer"));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }


    }
}

用semaphore 实现接口限流

切面注解SemaphoreDoc

package com.limiting.semaphore;

import java.lang.annotation.*;

@Documented

@Target({ElementType.METHOD})//作用:方法
@Retention(RetentionPolicy.RUNTIME)
public @interface SemaphoreDoc {
    String key(); //建议设置不然可能发生,不同方法重复限流现象
    int limit() default 3;

    int blockingTime() default 3;

}

切面类SemaphoreAop

@Component
@Aspect
public class SemaphoreAop {

    //这里需要注意了,这个是将自己自定义注解作为切点的根据,路径一定要写正确了
    @Pointcut(value = "@annotation(com.limiting.semaphore.SemaphoreDoc)")
    public void semaphoreDoc() {

    }
    //限流池
  private static  Map<String, Semaphore> map=new ConcurrentHashMap<>();


    @Around("semaphoreDoc()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        Object res = null;
        MethodSignature signature = (MethodSignature)joinPoint.getSignature();
        SemaphoreDoc annotation  = signature.getMethod().getAnnotation(SemaphoreDoc.class);
        int blockingTime = annotation.blockingTime();
        int limit = annotation.limit();
        String key = annotation.key();


        StringBuilder name = new StringBuilder(key+signature.getMethod().getName());//方法名
        for (String parameterName : signature.getParameterNames()) {
            name.append(parameterName);
        }

        Semaphore semaphore = map.get(name.toString());
        if (semaphore == null) {
            Semaphore semaphore1 = new Semaphore(limit);
            map.put(name.toString(),semaphore1);
            semaphore=semaphore1;
        }

        try {


            //获取令牌
            boolean b = semaphore.tryAcquire(blockingTime, TimeUnit.SECONDS);
            if (b) {//如果拿到令牌了那么执行方法
                try {
                    res = joinPoint.proceed();
                } catch (Throwable e) {
                    e.printStackTrace();
                }
            } else {
                //在一定时间内拿不到令牌那么就访问失败
                throw  new Exception("访问超时,目前请求人数过多请稍后在试");
            }

        } finally {
            //释放令牌,腾出位置
            semaphore.release();
        }

        return  res;
    }

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/5929.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot学习笔记上

文章目录1 SpringBoot1.1 SpringBoot介绍1.2 SpringBoot创建的三种方式1.3SpringBootApplication注解1.4 SpringBoot的配置文件1.5多环境配置1.6 使用jsp1.7 ComnandLineRunner 接口 &#xff0c; ApplcationRunner接口2 Web组件2.1 拦截器2.2 Servlet2.3 过滤器Filter2.4 字符…

gpt3官网中文版-人工智能软件chat gpt安装

GPT-3&#xff08;Generative Pre-trained Transformer 3&#xff09;是一种自然语言处理模型&#xff0c;由OpenAI研发而成。它是GPT系列模型的第三代&#xff0c;也是目前最大、最强大的自然语言处理模型之一&#xff0c;集成了1750亿个参数&#xff0c;具有广泛的使用场景&a…

Flutter Row 实例 —— 新手礼包

大家好&#xff0c;我是 17。 本文在 3.31 日全站综合热榜第一。 新手礼包一共 3 篇文章&#xff0c;每篇都是描述尽量详细&#xff0c;实例讲解&#xff0c;包会&#xff01; Flutter Row 实例 —— 新手礼包Flutter TextField UI 实例 —— 新手礼包Flutter TextField 交…

靠近用户侧和数据,算网融合实现极致协同

游弋自如的生产力&#xff0c;在边缘。IMMENSE、36氪&#xff5c;作者 1846年1月&#xff0c;纽约。 一行长短不一的电码顺着通讯线路飞往130公里开外的费城&#xff0c;这是华尔街的巨头们首次使用电报传输讯息&#xff0c;更具有金钱意味的是&#xff0c;电力通讯的成功&am…

【蓝桥杯集训·周赛】AcWing 第96场周赛

文章目录第一题 AcWing 4876. 完美数一、题目1、原题链接2、题目描述二、解题报告1、思路分析2、时间复杂度3、代码详解第二题 AcWing 4877. 最大价值一、题目1、原题链接2、题目描述二、解题报告1、思路分析2、时间复杂度3、代码详解第三题 AcWing 4878. 维护数组一、题目1、原…

路由策略实验

运行OSPF协议 [R1]ospf 1 router-id 1.1.1.1 [R1-ospf-1]area 0 [R1-ospf-1-area-0.0.0.0]network 192.168.12.1 0.0.0.0 [R1-ospf-1-area-0.0.0.0]network 192.168.13.1 0.0.0.0 [R2]ospf 1 router-id 2.2.2.2 [R2-ospf-1]area 0 [R2-ospf-1-area-0.0.0.0]network 192.168.…

抖音seo矩阵系统源码搭建技术+二开开源代码定制部署

抖音已经成为了当今最为流行的短视频平台之一&#xff0c;拥有着庞大的用户群体和海量的视频资源。对于一些商家或者运营者来说&#xff0c;如何从这些视频资源中挖掘出有效的信息&#xff0c;进而提升自己的品牌、产品或者内容的曝光度&#xff0c;就成为了一个非常重要的问题…

一次通过.frm和.ibd恢复mysql数据表的过程

1、导出.frm和.ibd文件 2、安装Mysql的Utilities 3、执行命令&#xff08;实际恢复的表&#xff09; mysqlfrm --diagnostic ./stat_vehicle_mileage.frm4、复制Sql&#xff0c;添加ROW_FORMATCOMPACT&#xff08;需要检测生成的Sql语句是否可用&#xff09; CREATE TABLE …

Android开发-Android常用组件-ProgressBar进度条

4.8 ProgressBar进度条 常用属性 android:max 进度条的最大值 android:progress 进度条已完成进度值 android:progressDrawable 设置轨道对应的Drawable对象 android:indeterminate 如果设置成true&#xff0c;则进度条不精确显示进度 android:indeterminateDrawable …

YOLO算法改进指南【算法解读篇】:2.如何训练自己的数据集

我们接着上一篇文章配置完YOLOv5需要的环境后,今天我们试着用YOLOv5训练自己的数据。(在开始本教程前,记得先跑一遍入门篇,确保环境是正常的) 有图有真相,先看看我的运行结果 【YOLOv5 源码地址】 🚀 我的环境: 语言环境:Python3.8编译器:PyCharm深度学习环境: to…

2021蓝桥杯真题格点(填空题) C语言/C++

问题描述 如果一个点(x,y) 的两维坐标都是整数, 即 x∈Z 且 y∈Z, 则称这个点为 一个格点。 如果一个点 (x,y) 的两维坐标都是正数, 即 x>0 且 y>0, 则称这个点在 第一象限。 请问在第一象限的格点中, 有多少个点(x,y) 的两维坐标乘积不超过 2021 , 即x⋅y≤2021 。 掟…

c#之反射详解

总目录 文章目录总目录一、反射是什么&#xff1f;1、C#编译运行过程2、反射与元数据3、反射的优缺点二、反射的使用1、反射相关的类和命名空间1、System.Type类的应用2、System.Activator类的应用3、System.Reflection.Assembly类的应用4、System.Reflection.Module类的应用5、…

SpringBoot 整合RabbitMq 自定义消息监听容器来实现消息批量处理

SpringBoot 整合RabbitMq 自定义消息监听容器来实现消息批量处理前言添加依赖配置文件编写监听器创建SimpleRabbitListenerContainerFactory发送消息前言 RabbitMQ是一种常用的消息队列&#xff0c;Spring Boot对其进行了深度的整合&#xff0c;可以快速地实现消息的发送和接收…

PCB模块化设计16——RS232,RS485接口模块PCB布局布线设计规范

目录PCB模块化设计16——RS232&#xff0c;RS485接口模块PCB布局布线设计规范RS232接口模块1、接口概述2、接口电路 原理图的EMC设计3、连接器设计4、线缆设计5、RS-232常规管脚定义&#xff1a;6、RS-232知识要点RS485接口模块1、原理图设计方案1、RS485接口6KV防雷电路设计方…

c语言程序笔记(1)

C语言笔记&#xff08;1&#xff09;——B站翁恺视频 程序框架 #include <stdio.h> int main() {//printf("hello world!\n");return 0; }1、变量与常量。 例子1&#xff1a; #include <stdio.h> int main() {printf("1234%d",1234);return …

图解LeetCode——合并两个有序链表

如果你喜欢这篇文章的话&#xff0c;请给作者点赞关注哟&#xff0c;你的支持是我不断前进的动力&#xff01; 目录 题目描述&#xff1a; 解法&#xff1a; 完整代码&#xff1a; 结果 题目链接&#xff1a;力扣 题目描述&#xff1a; 将两个升序链表合并为一个新的 升序…

2017世界互联网领先成果来了 光量子计算机

演讲者&#xff1a;陆朝阳中国科学技术大学教授 发布了世界上首台超越早期经典计算机的光量子计算机 陆朝阳&#xff1a;很高兴向大家报告中国科学院在量子计算这个领域取得的基础性的研究成果。 我们知道50多年以来摩尔定律一直见证着计算机的更新换代&#xff0c;之前每过18个…

【新2023Q2模拟题JAVA】华为OD机试 - 绘图机器

最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为od机试,独家整理 已参加机试人员的实战技巧本篇题解:绘图机器 题目 绘图机器的绘…

读书笔记-纳瓦尔宝典-2023.04.01

重点 财富 如何构造高价值信息 判断力 何为幸福 启发 最近看了这本书的大部分内容&#xff0c;感悟颇多&#xff0c;及时记录下来。 因为是快速阅读&#xff0c;还未做深入思考和实践&#xff0c;但对总体的内容有一个大致把握&#xff0c;未来会结合行动反复阅读和思考&…

python画爱心代码

前几天在网上看到了一个画爱心的教程&#xff0c;就是在 Python里面画一个爱心&#xff0c;但是我在网上找到的代码不是很好用&#xff0c;所以我就自己写了一遍。 首先我们先创建一个新的 python文件。新建一个 python文件夹&#xff0c;将我们之前的那个 python文件夹复制到这…