【源码解析】Semaphore角度聊聊AQS

案例

Semaphore,俗称信号量,它是操作系统中PV操作的原语在java的实现,它也是基于 AbstractQueuedSynchronizer实现的


    private static ExecutorService threadPool = Executors.newFixedThreadPool(4);
    private static Semaphore semaphore = new Semaphore(2);

    public static void main(String[] args) {
        for ( ; ; ) {
            threadPool.execute(()-> exec());
        }
    }

    public static void exec() {
        try {
            semaphore.acquire();
            System.out.println(Thread.currentThread().getName() + " before");
            TimeUnit.SECONDS.sleep(1);
            System.out.println("执行任务");
            System.out.println(Thread.currentThread().getName() + " after");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            semaphore.release();
        }

    }

从执行结果来看的话,先是2个线程获取到凭证,然后执行完毕。后续两个线程才开始获取凭证。

pool-1-thread-1 before
pool-1-thread-2 before
执行任务
执行任务
pool-1-thread-2 after
pool-1-thread-1 after
pool-1-thread-3 before
pool-1-thread-4 before
执行任务
执行任务
pool-1-thread-3 after
pool-1-thread-4 after

应用场景

Semaphore的使用场景主要用于一些中间件的时候,进行限流使用。

源码解析

构造方法

默认是非公平锁,可以通过构造参数进行设置。本篇主要介绍非公平锁的实现方式。

    public Semaphore(int permits) {
        sync = new NonfairSync(permits);
    }

    public Semaphore(int permits, boolean fair) {
        sync = fair ? new FairSync(permits) : new NonfairSync(permits);
    }
    // 非公平锁
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = -2694183684443567898L;

        NonfairSync(int permits) {
            super(permits);
        }

        protected int tryAcquireShared(int acquires) {
            return nonfairTryAcquireShared(acquires);
        }
    }
        Sync(int permits) {
            setState(permits);
        }

    // 设置state为构造方法的数值
    protected final void setState(int newState) {
        state = newState;
    }

获取凭证

            semaphore.acquire();
    public void acquire() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        //是否中断
        if (Thread.interrupted())
            throw new InterruptedException();
        // 线程等待
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }

T1线程直接获取锁,返回。T2线程也可以获取,但是T3线程进入的时候 state=0,获取不到锁。就会进入到 doAcquireSharedInterruptibly 这个逻辑中

        final int nonfairTryAcquireShared(int acquires) {
            for (;;) {
                获取当前state的值
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                    // //cas操作
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }

doAcquireSharedInterruptibly 其实就是将当前线程封装成一个Node节点,添加到AQS队列中。 shouldParkAfterFailedAcquire 会进行阻塞。

    private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        // 封装成一个node 加入AQS队列中 共享模式
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            //自选锁
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    // state 不等于0 返回-1
                    int r = tryAcquireShared(arg);
                    // 第一次不会进入
                    if (r >= 0) {
                        //  // 2. 这里将唤醒t3的后续节点t4,以此类推,t4被唤醒后,会在t4的await中唤醒t4的后续节点
                        setHeadAndPropagate(node, r);
                        // t3节点删除
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
                // 修改前驱节点waitstate = -1 挂起当前线程
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

释放凭证

    public void release() {
        sync.releaseShared(1);
    }
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

获取当前state的值,然后将state+=1 操作。

        protected final boolean tryReleaseShared(int releases) {
            for (;;) {
                int current = getState();
                int next = current + releases;
                if (next < current) // overflow
                    throw new Error("Maximum permit count exceeded");
                if (compareAndSetState(current, next))
                    return true;
            }
        }

释放资源。 unparkSuccessor(h); 会将T3线程进行唤醒。然后T3线程会尝试唤醒T4 (共享模式)。如果有资源的话,就获取锁,没有的话就会阻塞。

    private void doReleaseShared() {
        // 自选锁
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                // 前面已经将pre节点 设置为-1
                if (ws == Node.SIGNAL) {
                // 设置为0 
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    // 唤醒head的后继节点
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }

小结

通过代码进一步分析 可以更加了解Semaphore的原理。
在这里插入图片描述

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

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

相关文章

MySQL数据库,触发器、窗口函数、公用表表达式

触发器 触发器是由事件来触发某个操作&#xff08;也包含INSERT、UPDATE、DELECT事件&#xff09;&#xff0c;如果定义了触发程序&#xff0c;当数据库执行这些语句时&#xff0c;就相当于事件发生了&#xff0c;就会自动激发触发器执行相应的操作。 当对数据表中的数据执行…

进制之间的转换——n进制转换为m进制(C/C++实现,简单易懂)

目录 &#x1f308;前言&#xff1a; &#x1f4c1; 什么是进制转换&#xff1a; &#x1f4c1;其他进制转换成十进制&#xff1a; &#x1f4c2;二进制( B ) ——> 十进制( D ) &#x1f4c2;八进制( O ) ——> 十进制( D ) &#x1f4c2;十六进制( H ) ——> 十进制…

visual stdio code运行vue3

npm init vuelatest 该命令初始化vue项目 使用visual stdio code创建vue项目 ,这边是vue-project文件夹 vs code打开项目 vscode操作vue项目 vscode操作vue项目

数据分析思维导图

参考&#xff1a; https://zhuanlan.zhihu.com/p/567761684?utm_id0 1、数据分析步骤地图 2、数据分析基础知识地图 3、数据分析技术知识地图 4、数据分析业务流程 5、数据分析师能力体系 6、数据分析思路体系 7、电商数据分析核心主题 8、数据科学技能书知识地图 9、数据挖掘…

设计模式 简单工厂 工厂方法模式 抽象工厂模式 Spring 工厂 BeanFactory 解析

工厂模式介绍 工厂模式是我们最常用的实例化对象模式了&#xff0c;是用工厂方法代替new操作的一种模式。它是创建型模式。 简单工厂 简单工厂模式是指由一个工厂对象决定创建出哪一种产品类的实例, 但它不属于GOF 23种设计模式 简单工厂适用于工厂类负责创建的对象较少的场景,…

Spring IOC—基于注解配置和管理Bean 万字详解(通俗易懂)

目录 一、前言 二、基于注解配置Bean 1.基本介绍 : 2.应用实例 : 3.注意事项 : 三、手动实现Spring 注解配置机制 1.需求 : 2.思路 : 3.实现 : 3.1 自定义注解类 3.2 自定义配置类 3.3 自定义容器类 3.4 在测试类中进行测试 四、自动装配 0.总述 : 1.AutoWired自动装…

Autosar DEM DTC的Debounce策略

文章目录 简介Debounce策略1、基于计数器的 Debounce 策略2、基于时间的Debounce策略 简介 故障事件防抖&#xff0c;与按键防抖&#xff08;软件需要延时确认按键不是误触发&#xff09;的作用类似&#xff0c;目的是为了防止事件误触发采取的策略。 因为DTC并不是一达到触发…

Transformer Decoder的输入

大部分引用参考了既安的https://www.zhihu.com/question/337886108/answer/893002189这篇文章&#xff0c;个人认为写的很清晰&#xff0c;此外补充了一些自己的笔记。 弄清楚Decoder的输入输出&#xff0c;关键在于图示三个箭头的位置&#xff1a; 以翻译为例&#xff1a; 输…

2023.12.16 关于 分布式系统 基本介绍

目录 单机架构 服务器负载过高问题 解决方法 分布式系统 引入更多的服务器节点 负载均衡 数据库读写分离 引入缓存 数据库分库分表 引入微服务 基本概念 应用&#xff08;Application&#xff09;/ 系统&#xff08;System&#xff09; 模块&#xff08;Modul…

力扣日记12.18-【二叉树篇】合并二叉树

力扣日记&#xff1a;【二叉树篇】合并二叉树 日期&#xff1a;2023.12.18 参考&#xff1a;代码随想录、力扣 617. 合并二叉树 题目描述 难度&#xff1a;简单 给你两棵二叉树&#xff1a; root1 和 root2 。 想象一下&#xff0c;当你将其中一棵覆盖到另一棵之上时&#xf…

软件测试培训三个月,想找到工作,到底应该怎么搞

功能方面&#xff1a;问的最多的就是测试流程&#xff0c;测试计划包含哪些内容&#xff0c;公司人员配置&#xff0c;有bug开发认为不是 bug怎么处理&#xff0c;怎样才算是好的用例&#xff0c;测试用例设计方法&#xff08;等价类&#xff0c;边界值等概念方法&#xff09;&…

C# 使用FluentHttpClient请求WebApi

写在前面 FluentHttpClient 是一个REST API 异步调用 HTTP 客户端&#xff0c;调用过程非常便捷&#xff0c;采用流式编程&#xff0c;可以将所有请求所需的参数一次性发送&#xff0c;并直接获取序列化后的结果。 老规矩从NuGet上安装该类库&#xff1a; 这边一定要认准是 P…

python/c++ Leetcode题解——1.两数之和

目录 方法1:枚举法 思路 Code 方法2:哈希表 思路 Code 方法1:枚举法 思路 最容易想到的方法是枚举数组中的每一个数 x&#xff0c;寻找数组中是否存在 target - x。 当我们使用遍历整个数组的方式寻找 target - x 时&#xff0c;需要注意到每一个位于 x 之前的元素都已…

使用GPU利用ffmpeg-在Pyhton代码中实现视频转码到MP4格式的过程记录【失败告终-原因是显示型号太老不支持】

01-安装Nvida的显卡驱动和CUDA 参考文章 https://blog.csdn.net/wenhao_ir/article/details/125253533 进行安装。 02-下载ffmpeg的可执行文件 下载ffmpeg的Windows可执行文件&#xff0c;下载页面&#xff1a; https://www.gyan.dev/ffmpeg/builds/#release-builds 我在202…

深度学习中的潜在空间

1 潜在空间定义 Latent Space 潜在空间&#xff1a;Latent &#xff0c;这个词的语义是“隐藏”的意思。“Latent Space 潜在空间”也可以理解为“隐藏的空间”。Latent Space 这一概念是十分重要的&#xff0c;它在“深度学习”领域中处于核心地位&#xff0c;即它是用来学习…

ROS机器人入门

http://www.autolabor.com.cn/book/ROSTutorials/ 1、ROS简介 ROS 是一个适用于机器人的开源的元操作系统。其实它并不是一个真正的操作系统&#xff0c;其 底层的任务调度、编译、寻址等任务还是由 Linux 操作系统完成&#xff0c;也就是说 ROS 实际上是运 行在 Linux 上的次级…

微信小程序开发学习(基础)

学习课程&#xff1a;2023最新零基础入门微信小程序开发_哔哩哔哩_bilibili 微信开发工具下载地址&#xff1a;微信开发者工具下载地址与更新日志 | 微信开放文档 开发文档&#xff1a;微信开放文档 创建新项目 机型&#xff1a;iPhoneX 快捷键 <view>.row{$}*8 <…

Android hilt使用

一&#xff0c;添加依赖库 添加依赖库app build.gradle.kts implementation("com.google.dagger:hilt-android:2.49")annotationProcessor("com.google.dagger:hilt-android:2.49")annotationProcessor("com.google.dagger:hilt-compiler:2.49"…

对偶问题笔记(1)

目录 1 从 Lagrange 函数引入对偶问题2. 强对偶性与 KKT 条件3. 对偶性的鞍点特征 1 从 Lagrange 函数引入对偶问题 考虑如下优化问题 { min ⁡ f 0 ( x ) s . t f i ( x ) ≤ 0 , i 1 , ⋯ , p , h j ( x ) 0 , j 1 , ⋯ , q , x ∈ Ω , \begin{align} \begin{cases}\min…

Pipelined-ADC设计一:序言

现在是2023年12月18日&#xff0c;准备开新帖&#xff0c;设计一个 流水线型 模数转换器&#xff08; Pipelined-ADC &#xff09;。记录帖&#xff0c;后续会放在咸鱼。同步记录&#xff0c;谨防盗用。 初定指标&#xff1a;12位50Mhz&#xff0c;采用2.5bit每级结构&#xff…