并发工具类(三):Semaphore

news2024/9/24 13:21:49

1、Semaphore介绍

      Semaphore(信号量)也是juc包下一个线程同步工具,主要用于 在一个时刻允许多个线程

      对共享资源进行并行操作的场景。通常情况下,使用 Semaphore 的过程实际上是多个线程

      访问共享资源获取许可证的过程。

       Semaphore(信号量)底层也是基于AQS实现的,采用AQS的全局变量属性state作为一个

       计数器,state 的值就表示是Semaphore信号量资源个数。

        Semaphore 的内部逻辑如下:

                1)如果Semaphore 内部的计数器大于0,那么线程将可以获得小于该计数器数量的许可

                      证数量,同时还会导致 Semaphore 内部的计数器减少所发放的许可证数量。
                2)如果此时 Semaphore 内部的计数器等于0,表示没有可用的许可证,那么当前线程

                      可能被阻塞(使用 tryAcquire 方法时不回阻塞)
                 3)当前线程不再使用许可证时,需要立即将其释放以供其他线程使用,所以建议将

                      Semaphore 的获取和释放写在 try...finally...语句块中

2、Semaphore 核心属性&构造函数

      Semaphore 的核心属性只有一个Sync 类型的变量,Sync是 Semaphore  的一个内部类继承

      自AQS,所以Sync也是一个AQS;Semaphore中的所有功能都是基于Sync实现的。

       另外 Semaphore 还有2个Sync的子类,即 非公平模式实现的NonfairSync 和 基于公平模式

       实现的FairSync;所以 Semaphore 也是区分 “公平” 和 “非公平“”的。

       这里我们需要重点关注下 Semaphore 的构造函数,什么时候是公平的,什么时候是非公平的

       Semaphore 构造函数如下:

//默认是非公平的
//创建 Semaphore 时需要指定许可证数量(信号量数量)
public Semaphore(int permits) {
        //默认创建非公平锁
        sync = new NonfairSync(permits);
    }


public Semaphore(int permits, boolean fair) {
        sync = fair ? new FairSync(permits) : new NonfairSync(permits);
    }

3、Semaphore 应用场景

3.1、流量控制

         Semaphore 尝尝用于限制同时在线的用户数量

         如xx系统,最多允许指定数量的用户同时在线,如果超过指定数量,将告诉用户无法登录,

         请稍后再试,示例代码如下:

public class SemaphoreExample1 {

    public static void main(String[] args) {

        //定义允许登录数量
        final int MAX_PERMIT_LOGIN_ACCOUNT = 10;
        //在这里客户登录逻辑 LoginService 是公共资源
        final LoginService loginService = new LoginService(MAX_PERMIT_LOGIN_ACCOUNT);

        IntStream.range(0,20).forEach(i -> {
            new Thread(() -> {

                try{
                    //客户登录,实际上是获取一次信号量的操作
                    //boolean login = loginService.login();
                    //非阻塞的acquire() 方法
                    boolean login = loginService.loginByAcquire();
                    if(!login){
                        System.out.println(currentThread().getName()+" login Error");
                    }
                    //模拟登陆后的业务耗时
                    randomSleep();

                }finally {
                    //释放信号量
                    loginService.logout();
                }
            },"User-"+i).start();
        });

    }
    //随机休眠
    private static void randomSleep(){
        try {
            TimeUnit.SECONDS.sleep(current().nextInt(10));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //定义用户登录类
    private static class LoginService{
        //定义信号量
        private final Semaphore semaphore;

        public LoginService(int maxPermitLoginAccount){
            //初始化信号量,信号量计数器的数量值不能小于0
            this.semaphore = new Semaphore(maxPermitLoginAccount);
        }
        //登录方法,返回登录结果
        public boolean login(){
            //获取信号量,true=获取成功,false=获取失败
            //非阻塞方法,获取信号量失败不阻塞,直接返回false
            boolean login = semaphore.tryAcquire();
            if(login){
                System.out.println(currentThread().getName()+" login success.");
            }
            return login;
        }

        public boolean loginByAcquire(){
            try {
                //若获取信号量失败,则会阻塞,直到有资源可用
               semaphore.acquire();
                System.out.println(currentThread().getName()+" login success.");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return true;
        }


        public void logout(){
            //释放许可证
            semaphore.release();
            System.out.println(currentThread().getName()+" logout success.");
        }


    }
}

3.2、使用 Semaphore 模拟一个lock锁

         根据 Semaphore 的特性,只要没有可用的信号量(许可证),则当前线程需要挂起等待,

         等待其他线程释放信号量。只要new 创建 Semaphore 对象时,传入信号量的数量为1,那么

         Semaphore 就是一个lock锁,示例代码如下:

private static class TryLock{
        //Semaphore 中的计数器定义为1,同一时刻只能有一个线程获取到信号量
        private final Semaphore semaphore = new Semaphore(1);

        //能获取到信号量表示成功获取锁
        public boolean lock(){
            boolean lock = semaphore.tryAcquire();
            return lock;
        }

        //释放锁,即释放信号量
        public void unlock(){
            semaphore.release();
            System.out.println(currentThread().getName()+" release lock");
        }
    }

         

4、Semaphore 常用方法解析

4.1、acquire() 、acquire(int permits) 方法

         acquire() 和 acquire(int permits) 方法是获取信号量,若获取失败则一直挂起等待,直到获取

         信号量成功;若线程被中断则异常退出;不同的地方是 acquire() 获取一个信号量资源,而

         acquire(int permits) 获取指定数量permits的信号量资源,如下所示:

                     

                     

  4.2、acquireSharedInterruptibly(int arg) 方法

         acquireSharedInterruptibly 方法功能是获取可中断的共享锁;在获取锁之前先判断当前线程

         是否已经被其他线程中断,若已经被中断,则抛出中断异常并退出。

         若当前线程没有被中断,则执行 tryAcquireShared() ,若 tryAcquireShared() 方法执行失败

         ,即 tryAcquireShared() 返回值小于0,则将当前线程放入AQS双向链表中阻塞。

                  acquireSharedInterruptibly 方法代码如下:

                  

           在这里我们需要关注 tryAcquireShared() 方法在 AQS子类 Semaphore.NonfairSync

           和 Semaphore.FairSync 中的实现

4.2.1、tryAcquireShared() 在 Semaphore.NonfairSync 中的实现

            

4.2.2、tryAcquireShared() 在 Semaphore.FairSync 中的实现

/**
         * 以公平的模式 获取指定数量acquires的信号量
         * @param acquires
         * @return
         */
        protected int tryAcquireShared(int acquires) {
            //自旋
            for (;;) {
                //公平实现先判断队列中排队的情况
                //如果有排队节点,且当前节点不是头结点的next节点
                //则表示当前线程在AQS中的队列中排队,则返回-1,表示当前线程由AQS完成阻塞等待
                if (hasQueuedPredecessors())
                    return -1;
                //执行到这里表示当前线程不用排队,可以尝试竞争信号量资源
                //state变量用于表示permit值,获取当前资源数量
                int available = getState();
                int remaining = available - acquires;
                //remaining 小于0,表示没有足够的信号量资源,则直接返回remaining
                //remaining大于等于0,表示此时有多余的信号量,则进行CAS操作,若CAS操作失败,则进入下一次循环
                //若CAS操作成功,表示当前线程获取到了信号量资源,则返回当前的资源数,当前的资源数可能为0或者大于0
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }



 public final boolean hasQueuedPredecessors() {
        /**
         * 判断 是否存在比当前线程等待时间长的线程或节点(节点关联线程)
         */
        
        Node t = tail; //按反向初始化顺序读取节点
        Node h = head;
        Node s;
        //若队列不为空,且队列中除了头节点外第一个节点中关联的线程不是当前线程,
        //则返回true
        //todo 在并发条件下,在判断 h != t 成立后,后面 s = h.next 可能为空,这种情况下也认为存在有比当前线程等待时间长的线程
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());//todo 注意:在队列不为空的情况下,s一定不为空,
    }

4.3、nonfairTryAcquireShared(int acquires) 方法

         nonfairTryAcquireShared 方法是 Semaphore.Sync 类中的方法,用于非公平模式下获取

         指定数量的信号量,        

/**
         * 非公平锁获取共享锁(即读锁),直接抢锁
         * 这里是获取信号量
         *
         * @param acquires 要获取信号量的数量
         * @return
         */
        final int nonfairTryAcquireShared(int acquires) {
            //非公平锁直接CAS抢锁即可,直到可用资源小于0 或 CAS 抢到了锁
            for (;;) {
                int available = getState();
                int remaining = available - acquires;
                //可用资源数remaining小于0 或 CAS操作成功,即抢到了锁才会结束
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }

4.4、acquireUninterruptibly(int permits) 方法

         获取指定数量的信号量,若获取失败,则一直阻塞
         若线程被中断,则继续等待,即不允许中断

                 

          注意:acquireShared(int arg) 方法的解析请参考前边“AQS(二)共享锁的获取”

4.5、tryAcquire()、tryAcquire(int permits)  方法

         获取一指定数量的信号量资源,若有资源则返回true,若获取失败则返回false,
         注意:这个方法线程不会阻塞

                     

                     

         nonfairTryAcquireShared 方法请参考4.3

4.4、tryAcquire(long timeout, TimeUnit unit)、 tryAcquire(int permits, long timeout, TimeUnit unit)

         带有超时时间的获取指定数量的信号量资源,若获取成功,则直接返回true,都则线程挂起

          等待 ,若等待超过了超时时间 timeout ,则返回false

                  

                   

4.5、release() 、release(int permits)方法

         释放指定信号量,release() 默认是释放一个信号量

         

          

          注意:这里释放信号量的逻辑是在 Semaphore.Sync 中实现的

4.6、tryReleaseShared(int releases) 方法

         该方法功能是释放指定数量的信号量,是在 Semaphore.Sync 中实现的

/**
         * 释放锁(信号量)
         * @param releases
         * @return
         */
        protected final boolean tryReleaseShared(int releases) {
            //直接通过CAS操作对AQS的全局变量 state加上参数releases(releases一般都是1)即可
            for (;;) {
                //获取当前state,即信号量资源个数
                int current = getState();
                //机上要归还的信号量个数
                int next = current + releases;
                if (next < current) // 信号量小于加前的,即信号量溢出(超过int 最大值),则抛出溢出
                    throw new Error("Maximum permit count exceeded");
                if (compareAndSetState(current, next))//CAS操作成功,表示释放成功,则返回true,直接结束,否则进入下一次循环
                    return true;
            }
        }

4.7、availablePermits() 方法

        availablePermits 功能是获取当前可用的信号量个数

 public int availablePermits() {
        return sync.getPermits();
    }

//Sync方法
final int getPermits() {
            return getState();
        }
//AQS方法
protected final int getState() {
        //返回同步状态的当前值。该操作具有volatile读的内存语义。
        return state;
    }

        

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot中使用log遇到的一些问题

1、springboot logger.level与<logger>优先级&#xff1f; 百度结果如下&#xff1a; 所以得出结论&#xff0c;如果在yml种配置&#xff1a; logging:level:com.ruoyi: debugorg.springframework: warn 同时在logback-spring.xml中配置了 <!-- 系统模块日志级别控…

【无损检测】基于用深度学习的工业超声B-Scan 图像中的焊缝缺陷

Automated Weld Defect Detection in Industrial Ultrasonic B-Scan Images Using Deep Learning Abstract: 自动超声波检测&#xff08;AUT&#xff09;是一种无损检测&#xff08;NDT&#xff09;方法&#xff0c;广泛应用于具有重要经济意义的行业。为了确保对独有的 AUT 数…

数据结构与算法(3)栈和队列

1.前言 哈喽大家好啊&#xff0c;今天博主继续为大家带来数据结构与算法的学习笔记&#xff0c;今天是关于栈和队列&#xff0c;未来博主会将上一章《顺序表与链表》以及本章《栈与队列》做专门的习题应用专题讲解&#xff0c;都会很有内容含量 &#xff0c;欢迎大家多多支持&…

Facebook 小店:不出单?没流量?如何解决?

Facebook Marketplace为用户提供一个在本地交易商品的平台&#xff0c;包括二手商品、房屋出租和家政服务等都可以在上面检索到相关信息。据数据统计&#xff0c;每月约有4亿人使用Facebook Marketplace功能&#xff0c;潜力巨大&#xff0c;为商家提供了广阔的商机。然而&…

笔记 14 : 彭老师课本第 8 章, UART : 寄存器介绍 ,

&#xff08;99&#xff09; 继续介绍 uart 的关于通道的 一整套 寄存器&#xff0c; UCON 等&#xff1a; 接着介绍寄存器 UTRSTAT &#xff1a; 接着介绍读写数据的寄存器&#xff1a; 设置 uart 的波特率&#xff0c;有关的寄存器&#xff1a; &#xff08;100&#xf…

【Linux】进程优先级|进程切换

目录 进程优先级 1️⃣ 进程优先级 1.什么是进程优先级&#xff1f; 2.进程优先级的类型 3.进程优先级的作用 4.进程优先级的实现 5.进程优先级的重要性 6.查看系统进程 7.修改进程优先级 top命令进行修改 renice命令 8.优先级调度原理​编辑 进程切换 进程优先级…

1985-2022年全国土地利用分类可视化数据(30M)

1985-2022年全国土地利用分类可视化数据&#xff08;30M&#xff09; 1、时间&#xff1a;1989-2022年 2、空间分辨率&#xff1a;30米&#xff0c; 3、地理坐标系&#xff1a;WGS-84 4、土地类型&#xff1a;九种 类型编码含义1&#xff1a;农田&#xff1b;2&#xff1a…

Windows电脑本地安装HFS文件共享服务结合内网穿透搭建低成本NAS

文章目录 前言1.软件下载安装1.1 设置HFS访客1.2 虚拟文件系统 2. 使用cpolar建立一条内网穿透数据隧道2.1 保留隧道2.2 隧道名称2.3 创建二级子域名访问本地hfs 总结 前言 本文主要介绍如何在Windows系统电脑使用HFS并结合cpolar内网穿透工具搭建低成本NAS&#xff0c;并实现…

指针之旅(3)—— 指针 与 数组

目录 1. 数组名的两种意义 2. 指针访问数组&#xff08;指针也能下标引用&#xff09; 3. 一维数组传参的本质 和 sizeof在函数中失效的原因 4. 指针数组 4.1 指针数组的概念 4.2 一级指针数组 4.3 一级指针数组模拟实现二维数组 5. 数组、指针 与 字符串 6. 数组指针…

微信小程序实现文件的预览

1&#xff0c;最简单的直接使用<web-view src网络文件地址><web-view>文件如果有在线地址&#xff0c;直接用web&#xff0c;但是要在小程序的管理平台中增加文件地址到业务域名当中 2、使用微信本身自带方法 图片预览 wx.previewImage({urls:[this.data.ossPath…

【数据结构】【java】leetcode刷题记录--链表

简介 链表是一种常见的基础数据结构&#xff0c;它由一系列节点组成&#xff0c;每个节点包含数据域和指向下一个节点的指针。在Java中&#xff0c;链表通常用于实现动态数据结构&#xff0c;因为它可以根据需要动态地增加或减少节点。 链表简介&#xff1a; 节点结构&#…

如何为 DigitalOcean 静态路由操作员设置故障转移

静态路由操作器的主要目的是提供更大的灵活性&#xff0c;并在 Kubernetes 环境中控制网络流量。它使你能够根据应用程序的需求自定义路由配置&#xff0c;从而优化网络性能。该操作器作为 DaemonSet 部署&#xff0c;因此将在你的 DigitalOcean Managed Kubernetes 集群的每个…

以太坊基金会AMA总结:面对ETH价格疲软,团队的应对策略与展望

2024年9月5日晚&#xff0c;以太坊基金会举行了第12届AMA&#xff08;Ask Me Anything&#xff09;活动。本次活动在Twitter Space上进行&#xff0c;由以太坊联合创始人Vitalik Buterin、基金会成员Justin Drake和Dankrad Feist等核心成员参与&#xff0c;针对社区关心的多个问…

【YashanDB知识库】表数据量不多,lob数据段有大量空间,插入数据报错

问题现象 clob段异常增长&#xff0c;导致磁盘空间满&#xff0c;应用无法使用数据库。 问题风险及影响 lob段空间未复用&#xff0c;lob段空间扩张很大&#xff0c;影响磁盘占用合理分配。 空间不够&#xff0c;插入报错&#xff0c;影响业务。 问题影响的版本 所有版本…

全视通智慧病房系统旧病房改造方案

一、背景介绍 在当今医疗技术日新月异的时代&#xff0c;智慧病房作为医院现代化建设的重要一环&#xff0c;正逐步从概念走向现实&#xff0c;深刻改变着患者的就医体验与医护人员的工作模式。智慧病房的改造背景&#xff0c;根植于医疗需求的日益增长、技术创新的不断推动以及…

自定义事件分发

一、在C中创建可接收事件的接口类EventInterface&#xff0c;继承自UInterface 1、EventInterface.h #pragma once #include "CoreMinimal.h" #include "UObject/Interface.h" #include "EventInterface.generated.h" UINTERFACE(MinimalAPI) c…

页面小组件-搜索栏(二)-未经项目验证,慎重!!!

前言说明 这一版是未经过项目验证的&#xff0c;可能会有地方需要自行调整&#xff0c;如需使用&#xff0c;请慎重、慎重、再慎重&#xff01;&#xff01;&#xff01; 前言追溯 前面分享过的搜索栏组件是一个临时产物&#xff0c;经历了一两个项目之后就被淘汰了。后续在…

Aloudata AIR :国内首个 Data Fabric 逻辑数据平台

AIR 的寓意是“极致轻盈的数据交付”&#xff1a;A - Adaptive 自适应&#xff0c;I - Integration 集成&#xff0c;R - Resilience 弹性 News&#xff1a;Aloudata AIR 发布 作为国内首个 Data Fabric 逻辑数据平台&#xff0c;Aloudata AIR 通过自研的数据虚拟化技术&#…

使用树莓派学习——Linux库编程

树莓派开发——Linux静态动态库 文章目录 树莓派开发——Linux静态动态库一、分文件编程1.1 分文件编程的优点&#xff1a;1.2 分文件编程的步骤&#xff1a; 二、Linux的库2.1 函数库的概念&#xff1a;2.2 静态库和动态库的比较&#xff1a;静态数据库&#xff08;libXXX.a&a…

奖项再+1!通义灵码智能编码助手通过可信 AI 智能编码工具评估,获当前最高等级

阿里云的通义灵码智能编码助手参与中国信通院组织的可信AI智能编码工具首轮评估&#xff0c;最终获得 4 级评级&#xff0c;成为国内首批通过该项评估并获得当前最高评级的企业之一。 此次评估以《智能化软件工程技术和应用要求 第 2 部分&#xff1a;智能开发能力》为依据&…