一看就懂的Semaphore源码解析,诸佬们快来看看吧

news2025/8/2 20:37:42

前言:一位朋友问到了我Semaphore类相关的知识,简单看了一下源码复习了一下,写下本篇文章做一个回顾。
希望能够加深自己的印象以及帮助到其他的小伙伴儿们😉😉。
如果文章有什么需要改进的地方还请大佬不吝赐教👏👏。
小威在此先感谢各位大佬啦~~🤞🤞

在这里插入图片描述

🏠个人主页:小威要向诸佬学习呀
🧑个人简介:大家好,我是小威,一个想要与大家共同进步的男人😉😉
目前状况🎉:24届毕业生,曾经在某央企公司实习,目前在某税务公司实习👏👏

💕欢迎大家:这里是CSDN,我总结知识的地方,欢迎来到我的博客,我亲爱的大佬😘

在这里插入图片描述

以下正文开始

在这里插入图片描述

文章目录

  • Semaphore简单介绍
  • Semaphore案例详解
  • 源码剧透
    • 获取许可(acquire)
    • 释放许可(release)
  • 小结

Semaphore简单介绍

Semaphore,在中文中有信号量的意思,它被用来限制能同时访问共享资源的线程上限。

可以将Semaphore比喻为停车场,permits(许可)好比停车位的数量,线程好比汽车,这个过程就好像我们限制了停车场汽车停放的数量。当每个汽车获取一个停车位,停车场可共享的停车位数量将减一,反之加一。

通过一个小案例看下Semaphore的用法,然后再进行源码分析。

Semaphore案例详解

首先,我们新建一个semaphore对象,并传入信号量的值为3,代表能同时访问共享资源的线程上限为3;接着循环创建10个线程,让每个线程获取信号量资源,等待线程运行1秒后,释放信号量资源,代码如下:

public class TestSemaphore {
    public static void main(String[] args) {
        //创建semaphore对象
        Semaphore semaphore = new Semaphore(3);
        for (int i=0;i<10;i++){
            new Thread(()->{
                try{
                    //获取信号量资源
                    semaphore.acquire();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                try {
                    System.err.println(Thread.currentThread().getName()+"正在运行中……");
                    sleep(1000);
                    System.out.println(Thread.currentThread().getName()+"运行已结束");
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                } finally {
                    //释放信号量资源
                    semaphore.release();
                }
            }).start();
        }
    }
}

运行测试案例可以得到下面的结果:
在这里插入图片描述
由此可见,semaphore限制了同时访问共享资源的线程数,每次只能三个线程获取共享资源。
在这里插入图片描述

源码剧透

在前面的文章中详细记录过AQS(Abstract Queued Synchronizer)的知识,不得不说AQS是真的强大,在Semaphore类的源码实现中也用到了AQS的原理,这点知识忘记的大佬可以看下前面AQS的文章。

获取许可(acquire)

讲到线程获取许可,还需要从Semaphore的构造方法详细说起,Semaphore类位于JUC(java.util.concurrent)包下:

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

构造方法中新建了一个非公平的同步器方法,并将许可值传入了进去,点入NonfairSync( )一看,大吃一惊!

    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = -2694183684443567898L;
        NonfairSync(int permits) {
            super(permits);
        }
}

它居然调用的是父类的方法,耐住性子,点入super发现,我的老天爷,简直不敢看:

    abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 1192457210091910933L;

        Sync(int permits) {
            setState(permits);
        }

        final int getPermits() {
            return getState();
        }

从这块源代码中可以看出,同步器类继承了AQS,Semaphore’类的底层实现还是依靠了AQS先进先出的阻塞队列。而且传入的permits许可值被赋予了AQS的state状态字段。

当线程获取资源时调用的acquire()方法,我们搜索acquire看看源码吧:

    public void acquire() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

上述代码表示线程可中断地获取1个许可值,点入acquireSharedInterruptibly(1)方法来看看它的实现:

    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }

首先对当前线程进行一个判断,如果当前线程被中断,那么直接抛出异常,如果没有,则会调用tryAcquireShared(arg)方法,此方法是由子类具体实现的。在之前的AQS文章中,尝试获取共享资源时记录过这块的知识。如果tryAcquireShared(arg) 方法返回的是大于0的数,表示获取成功,如果线程获取许可失败,该方法返回值表示剩余的资源数,tryAcquireShared(arg)方法如下:

protected int tryAcquireShared(int arg) {
    throw new UnsupportedOperationException();
}

这个方法有四个具体的实现(四大天王),我们点入想看的Semaphore实现类中去:

        protected int tryAcquireShared(int acquires) {
            return nonfairTryAcquireShared(acquires);
        }
    }

上述代码又调用到了nonfairTryAcquireShared(),点入 nonfairTryAcquireShared(非公平尝试获取共享资源)方法查看得到:

        final int nonfairTryAcquireShared(int acquires) {
            for (;;) {
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }

这段代码意思是,拿到state的状态值,减去传入的许可数,remaining就是剩余的许可数,如果remaining不小于0,则调用CAS机制,安全的更改state的值,将remaining返回。

如果当前state状态为0,减去acquire之后变成负数,接下来判断remaining<0为TRUE,不会在CAS更新state的值,短路,直接返回remaining(负数)。

如果返回的remaining小于0,则会调用doAcquireSharedInterruptibly(arg)方法,这里又涉及到了AQS中的常用方法,这里可以看注释理解:

    /**
     * Acquires in shared interruptible mode.
     * @param arg the acquire argument
     */
    private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        //将节点加入阻塞队列尾部
        final Node node = addWaiter(Node.SHARED);
        //是否获取到许可,当前表示没有获取到
        boolean failed = true;
        try {
            //自旋
            for (;;) {
                找到当前节点的前驱节点
                final Node p = node.predecessor();
                if (p == head) {
                    //看看是否能够获取到许可,和上面的方法一样
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        //如果获取到许可,唤醒node节点,释放p(头结点)
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
                //如果前面没有获取成功,将线程阻塞住并将state设置为-1
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

private Node addWaiter(Node mode) {
       
        /* 这个可以参考上面Node的构造方法
        Node(Thread thread, Node mode) {     // Used by addWaiter
            this.nextWaiter = mode;
            this.thread = thread;
        }
        */
        
        //构造新的等待线程节点
        Node node = new Node(Thread.currentThread(), mode);
       
        //新建临时节点pred指向尾节点
        Node pred = tail;
        //队列不为空的话,通过CAS机制将node放到队列尾部
        if (pred != null) {
            //将node的prev域指向尾节点
            node.prev = pred;
            //通过CAS机制将node放到队列尾部
            if (compareAndSetTail(pred, node)) {
                //将原来尾节点的next域指向当前node节点,node现在为尾节点
                pred.next = node;//形成双向链表
                return node;
            }
        }
        //如果队列为空的话
        enq(node);
        return node;
    }

在多线程并发情况下,如果有多个线程同时争夺尾节点的位置,会调用enq(node)方法,使用CAS自旋机制挂到双向链表的尾部,下面是源码:

    private Node enq(final Node node) {
        //死循环(自旋)
        for (;;) {
            Node t = tail;
            //尾节点为null,说明头结点也为null,可能是还没有创建队列的时候
            if (t == null) { 
                //多线程并发情况下,利用CAS机制创建头结点和尾节点,CAS保证此时只有一个头节点被创建,下次自旋时,就会满足队列不为空的条件
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                //如果存在尾节点,将当前节点的prev域指向尾节点
                node.prev = t;
                //利用CAS机制完成双向链表的绑定,让之前尾节点指向当前node节点
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

接下来看一看compareAndSetTail方法使用CAS乐观锁机制的方法源码:

    private final boolean compareAndSetTail(Node expect, Node update) {
        return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
    }

如果当前的前驱节点表示头结点,并且获取资源成功,那么直接将当前线程设为头结点,释放之前头结点与后继节点的链接,帮助垃圾回收(GC),如果前面当前节点的前驱不为头结点或者没有获取到资源,那么会调用shouldParkAfterFailedAcquire(Node pred, Node node)方法来判断当前线程是否能够进入waiting状态,如果可以进入,并且进入到了阻塞状态,那会阻塞,直到调用了LockSupport中的unpark()方法唤醒线程。

    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        //保存前驱节点的状态
        /*
        提示:
        当waitState>0时,表示该线程处于取消状态(线程中断或者等待锁超时),需要移除线程;
        当waitState=0时,默认值,表示初始化状态,表示线程还未完成初始化操作;
        当waitState<0,表示有效状态,线程处于可唤醒状态。
        */
        int ws = pred.waitStatus;
        //等待唤醒后置节点,SIGNAL为-1
        if (ws == Node.SIGNAL)
            return true;
        //如果前置节点不是正常的等待状态(CANCELLED结束状态),那么从当前节点开始往前寻找正常的等待状态
        if (ws > 0) {
            do {
                //后面的节点断开与前驱节点的链接
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            //双向连接
            pred.next = node;
        } else { //小于0时,可能为共享锁
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

如果前驱节点的SIGNAL值为-1,会返回true。

compareAndSetWaitStatus(pred, ws, Node.SIGNAL)方法内部也使用了CAS锁机制,源码:

    private static final boolean compareAndSetWaitStatus(Node node,
                                                         int expect,
                                                         int update) {
        return unsafe.compareAndSwapInt(node, waitStatusOffset,
                                        expect, update);
    }

如果shouldParkAfterFailedAcquire(Node pred, Node node)方法返回true,则会调用parkAndCheckInterrupt()方法阻塞当前线程,线程等待,如果线程被中断过则返回true:

private final boolean parkAndCheckInterrupt() {
    // 调用park让线程进入wait状态
    LockSupport.park(this);
    // 检查线程是否中断过。
    return Thread.interrupted();
}

如果线程在等待的过程中被中断过,那么获取到资源后会通知线程中断:

    /**
     * Convenience method to interrupt current thread.
     */
    static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }

在这里插入图片描述

释放许可(release)

在Semaphore类中找到release()方法,

    public void release() {
        sync.releaseShared(1);
    }

调用releaseShared(1)方法释放1个许可,点入releaseShared(1)方法的:

    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

继续点入tryReleaseShared(arg)查看源码得:

    protected boolean tryReleaseShared(int arg) {
        throw new UnsupportedOperationException();
    }

在这里插入图片描述
这个类有三个实现类,点击Semaphore的实现类中查看方法:

        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;
            }
        }

这个方法和上面获取许可时的方法类似,也是获取当前的状态值,将当前状态值和释放的许可值相加,最后调用CAS方法替换返回即可。

替换成功后,返回true,将继续向下进行doReleaseShared()方法,这个也在AQS中详细提到过,这里由于篇幅原因就不再复述了。

在这里插入图片描述

小结

其实从此可以看出,Semaphore类大多数用到了AQS源码的相关知识,因此AQS的知识还是挺重要的,这块也是面试中的重点,希望我们都能把握住哦~~~

好了,本篇文章就先分享到这里了,后续会继续分享其他方面的知识,感谢大佬认真读完支持咯~
在这里插入图片描述

文章到这里就结束了,如果有什么疑问的地方请指出,诸佬们一起讨论😁
希望能和诸佬们一起努力,今后我们顶峰相见🍻
再次感谢各位小伙伴儿们的支持🤞

在这里插入图片描述

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

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

相关文章

华为OD机试题,用 Java 解【航天器】问题

最近更新的博客 华为OD机试 - 猴子爬山 | 机试题算法思路 【2023】华为OD机试 - 分糖果(Java) | 机试题算法思路 【2023】华为OD机试 - 非严格递增连续数字序列 | 机试题算法思路 【2023】华为OD机试 - 消消乐游戏(Java) | 机试题算法思路 【2023】华为OD机试 - 组成最大数…

CSCode 配置一条龙 CPP/CC

下载 官⽹下载地址&#xff1a;Download Visual Studio Code - Mac, Linux, Windows 下载太慢&#xff0c;推荐⽂章&#xff1a;解决VsCode下载慢问题_wang13679201813的博客-CSDN博客_vscode下载慢 安装 无脑下一步 推荐插件 免配置&#xff1a; 1. Remote - SSH - 远程…

Exception has occurred: ModuleNotFoundErrorNo module named ‘urllib3‘【已解决】

问题描述 实际上只是想要测试一下torch是否安装成功&#xff0c;输出相应版本。谁知道就报错了。 Exception has occurred: ModuleNotFoundError No module named urllib3 解决方案 &#xff08;1&#xff09;使用pip或者conda卸载urllib3 pip uninstall urllib3conda unin…

离散无记忆与有记忆信源的序列熵

本专栏包含信息论与编码的核心知识&#xff0c;按知识点组织&#xff0c;可作为教学或学习的参考。markdown版本已归档至【Github仓库&#xff1a;information-theory】&#xff0c;需要的朋友们自取。或者公众号【AIShareLab】回复 信息论 也可获取。 文章目录离散无记忆信源的…

以高能低碳技术融入PC全生命周期,英特尔联合业界推出绿色商用电脑

双碳既是关系到地球上每个人的大话题&#xff0c;也是IT系统和产品降本增效的重要手段。 英特尔将高能低碳新理念融入从PC定义设计到回收循环的全生命周期 4 大关键环节&#xff0c;值得参考。 碳达峰、碳中和这个“双碳”的话题貌似与技术开发者个人距离很远。其实&#xff0c…

骨传导耳机是怎么传声的,选择骨传导耳机的时候需要注意什么?

​骨传导耳机之所以能够成为当下最火的耳机&#xff0c;骨传导技术将声音转化为震动感&#xff0c;通过骨头进行传播&#xff0c;不会堵塞耳朵&#xff0c;就不会影响到周围环境音。这种技术也让骨传导耳机比传统入耳式耳机更安全&#xff0c;无需入耳式设计&#xff0c;避免了…

小猫小狗玩数学-第14届蓝桥杯STEMA测评Scratch真题精选

[导读]&#xff1a;超平老师的《Scratch蓝桥杯真题解析100讲》已经全部完成&#xff0c;后续会不定期解读蓝桥杯真题&#xff0c;这是Scratch蓝桥杯真题解析第102讲。 蓝桥杯选拔赛现已更名为STEMA&#xff0c;即STEM 能力测试&#xff0c;是蓝桥杯大赛组委会与美国普林斯顿多…

蓝牙运动耳机哪个好,比较好的运动蓝牙耳机

很多想选择蓝牙运动耳机的朋友都不知道应该如何选择&#xff0c;运动首先需要注意的就是耳机的防水能力以及耳机佩戴舒适度&#xff0c;在运动当中会排出大量的汗水&#xff0c;耳机防水等级做到越高&#xff0c;可以更好地保护耳机不受汗水浸湿&#xff0c;下面就分享五款适合…

智能电视“套娃式”收费背后的自我救赎

配图来自Canva可画 近年来随着智能化浪潮的迅速铺开&#xff0c;与以前只能看电视的智能电视相比&#xff0c;现在的智能电视还能打游戏、听音乐&#xff0c;用户还可在电视上自行下载、安装、卸载应用软件&#xff0c;功能大大丰富了。但随着智能电视功能的逐渐增多&#xff…

我们应该如何优雅的处理 React 中受控与非受控

引言 大家好&#xff0c;我是19组清风。有段时间没有和大家见面了&#xff0c;最近因为有一些比较重要的事情&#xff08;陪女朋友和换了新公司&#xff09;在忙碌所以销声匿迹了一小段时间&#xff0c; 后续会陆陆续续补充之前构建 & 编译系列中缺失的部分&#xff0c;提…

day 33 状态压缩dp

二维状态压缩dp对于解决哈密顿回路问题的状态压缩dp只能计算固定起点到其他点的总方案数或最小路径等回路计数小蓝现在在第一栋教学楼&#xff0c;他想要访问每栋教学楼正好一次&#xff0c;最终回到第一栋教学楼&#xff08;即走一条哈密尔顿回路&#xff09;可看做&#xff1…

华为OD机试题,用 Java 解【计算面积】问题

最近更新的博客 华为OD机试 - 猴子爬山 | 机试题算法思路 【2023】华为OD机试 - 分糖果(Java) | 机试题算法思路 【2023】华为OD机试 - 非严格递增连续数字序列 | 机试题算法思路 【2023】华为OD机试 - 消消乐游戏(Java) | 机试题算法思路 【2023】华为OD机试 - 组成最大数…

索引的创建与设计原则

1.索引的声明与使用 1.1索引的分类 MySQL的索引包括普通索引、唯一性索引、全文索引、单列索引、多列索引和空间索引等。 从 功能逻辑 上说&#xff0c;索引主要有 4 种&#xff0c;分别是普通索引、唯一索引、主键索引、全文索引。按照 物理实现方式&#xff0c;索引可以分…

【编程基础之Python】6、Python基础知识

【编程基础之Python】6、Python基础知识Python基础知识Python的基本要素模块语句表达式注释Python的代码格式Python基础知识 Python 是一种高级的、动态的、解释型的编程语言&#xff0c;具有简单易学、开发效率高、可读性强等特点&#xff0c;广泛应用于数据科学、Web 开发、…

CRM系统能帮外贸行业解决哪些问题

国内的外贸行业经历了四个发展阶段&#xff0c;从发展期到繁荣期&#xff0c;CRM客户管理系统逐步走到幕前&#xff0c;成为外贸企业必不可少的主打工具。那么外贸行业整面临哪些问题&#xff1f;该如何解决&#xff1f;下面我们就来说说适合外贸行业的CRM解决方案。 外贸行业…

数据分享|2023年OSM道路数据(全国,分省,分市)

道路数据是我们在各项研究中经常使用的数据!道路数据虽然很常用,但是却基本没有能下载最近年份道路数据的网站,所以很多人不知道如何获到道路数据。 本次我们为大家推荐的下载道路数据的网站是Open Street Map!我们先来了解下Open Street Map,其简称为OSM,是一个开源的地…

整数保序的离散化(C/C++)

目录 1. 离散化的概念 1.1 离散化的运用思路 1.2 离散化的方法 1.2.1 排序 1.2.2 确定一个元素离散化后的结果 1.3 案例分析 1.3.1 1.3.2 区间和 &#xff08;来源&#xff1a;Acwing&#xff09; 1. 离散化的概念 离散化&#xff0c;把无限空间中有限的个体映射到有限的…

机房信息牌系统

产品特色&#xff1a; 无线低功耗安装简单&#xff0c;快速布置易于维护墨水屏显示&#xff0c;清晰&#xff0c;更环保信息后台推送&#xff0c;远程管理多模版样式随意制作多尺寸&#xff1a;4.2寸&#xff0c;7.5寸&#xff0c;10.2寸4.2寸7.5寸10.2寸标签特性&#xff1a;…

每日一个小技巧:教你如何使用终端工具给你的电脑发送弹窗提醒

现在人手一部智能手机&#xff0c;这些智能手机都有个非常实用的功能&#xff0c;那就是弹窗提醒。当我们收到短信&#xff0c;或者微信信息时&#xff0c;手机就会弹窗显示信息的大致内容。有了这个功能你就不会错过重要信息了。 电脑上也有类似的功能&#xff0c;也很实用。…

改进YOLO系列 | 添加轻量化Decouple_Head 和 ASFF_Head

绿色为ASFF_Head,浅蓝色Decoupled_Head,深蓝色是第三步加的_initialize_dh_biases方法后的效果。 参数量与计算量对比 模型参数量 parameters计算量GFLOPsyolov5s_Head723538916.5ASFF_Head1267484725.0Decoupled_Head892869722.0结构图 本篇介绍的这个Decouple_Head和YOLOX…