简介

Semaphore 又名计数信号量,从概念上来讲,信号量初始并维护一定数量的许可证,使用之前先要先获得一个许可,用完之后再释放一个许可。信号量通常用于限制线程的数量来控制访问某些资源,从而达到单机限流的目的,比如SpringCloud 中的Zuul 组件用的是 Hystrix 的信号量(semaphore)隔离模式。

源码分析

重要的内部类

Semaphore 和 ReentrantLock 内部类完全相似, 有3个重要的内部类,分别也是 SyncNonfairSyncFairSync

  1. Sync 是后面两个的父类,继承至AbstractQueuedSynchronizer(AQS)
  2. NonfairSync和FairSync都继承至Sync
  3. NonfairSync 主要用于实现非公平锁,FairSync 主要用于实现公平锁

如果你看了前面几天关于锁的源码分析,是不是发现它们的套路都差不多呢?

重要的属性

和 ReentrantLock 也完全一样,只有一个重要的属性,同步器sync:

  1. private final Sync sync;

两个构造方法

  1. // ①指定初始许可证数量
  2. public Semaphore(int permits) {
  3. sync = new NonfairSync(permits);
  4. }
  5. // ②指定初始许可证数量和公平模式
  6. public Semaphore(int permits, boolean fair) {
  7. sync = fair ? new FairSync(permits) : new NonfairSync(permits);
  8. }

两个构造方法最后都是初始化许可证数量,调用的也就是同步器里面的构造方法来初始化AQS 里面的state字段

  1. // Sync 的构造方法
  2. Sync(int permits) {
  3. setState(permits);
  4. }
  5. // AQS 中的代码
  6. protected final void setState(int newState) {
  7. state = newState;
  8. }

获取许可:acquire()

默认每次获得1个许可,如果没有可用的许可证会阻塞线程,或者被中断抛出异常。

源码分析:

  1. public void acquire() throws InterruptedException {
  2. sync.acquireSharedInterruptibly(1); // 默认每次获得1个许可
  3. }

acquireSharedInterruptibly(1)会调用 AQS 里面的方法:

  1. public final void acquireSharedInterruptibly(int arg)
  2. throws InterruptedException {
  3. if (Thread.interrupted()) // 线程被中断,抛出异常
  4. throw new InterruptedException();
  5. if (tryAcquireShared(arg) < 0) // tryAcquireShared 尝试获得许可,返回小于0 表示没有获得许可
  6. doAcquireSharedInterruptibly(arg); // 没有获得许可,排队阻塞
  7. }

tryAcquireShared(arg)方法:

tryAcquireShared 有两种实现,也就是 FairSync(公平模式) 和 NonfairSync(非公平模式) 不同实现。

  1. 公平模式的实现代码 FairSync.tryAcquireShared

    1. // acquires
    2. protected int tryAcquireShared(int acquires) {
    3. for (;;) { // 自旋
    4. if (hasQueuedPredecessors()) // 检查是否有更早的线程在排队获得许可
    5. return -1; // 有排队的线程,返回-1,小于0表示获得许可失败
    6. int available = getState(); // 获得可用许可数
    7. int remaining = available - acquires; // 减去一个许可,计算剩余的许可数
    8. if (remaining < 0 || compareAndSetState(available, remaining))
    9. // remaining < 0 成立的话就说明获取许可失败了,出去也要排队阻塞线程
    10. return remaining;
    11. }
    12. }
  2. 非公平模式的实现代码NonfairSync.tryAcquireShared:

    1. protected int tryAcquireShared(int acquires) {
    2. return nonfairTryAcquireShared(acquires); // 调用父类Sync里面的实现方法
    3. }
    4. // 父类Sync里面的实现方法
    5. final int nonfairTryAcquireShared(int acquires) {
    6. for (;;) { // 自旋
    7. int available = getState(); // 获得可用许可数
    8. int remaining = available - acquires; // 减去一个许可,计算剩余的许可数
    9. if (remaining < 0 || compareAndSetState(available, remaining))
    10. // remaining < 0 成立的话就说明获取许可失败了,出去也要排队阻塞线程
    11. return remaining;
    12. }
    13. }

    有没有发现他们的代码非常相识?公平模式的实现就只是比非公平模式多了一个hasQueuedPredecessors() 方法调用判断,这个方法主要就是检查排队的队列里面是不是还有其他线程。在之前分析ReentrantLock 源码的文章中也有提到。

如果tryAcquireShared 方法没有获得许可(返回值小于0),就会进入到AQS 的 doAcquireSharedInterruptibly 方法:

  1. private void doAcquireSharedInterruptibly(int arg)
  2. throws InterruptedException {
  3. // 为当前线程创建排队节点,并加入到队列
  4. // addWaiter方法的分析在之前的AQS分析文章已经分析过了
  5. final Node node = addWaiter(Node.SHARED);
  6. boolean failed = true;
  7. try {
  8. for (;;) { // 自旋,尝试获得许可,阻塞线程,唤醒后继续获得许可
  9. final Node p = node.predecessor();
  10. if (p == head) {
  11. int r = tryAcquireShared(arg); // 尝试获得许可
  12. if (r >= 0) { // 获得许可
  13. setHeadAndPropagate(node, r); // 设置排队的头节点
  14. p.next = null; // help GC
  15. failed = false;
  16. return; // 线程获得许可,退出
  17. }
  18. }
  19. // shouldParkAfterFailedAcquire 如果线程应阻塞,则返回true
  20. // 之前的AQS分析文章已经分析过了
  21. if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
  22. // 被中断了,抛出异常
  23. throw new InterruptedException();
  24. }
  25. } finally {
  26. if (failed) // 节点被取消
  27. cancelAcquire(node);
  28. }
  29. }

获得许可总结:

  1. 获得许可就是对初始化的许可证进行减1,直到没有许可证了就会进入到队列排队阻塞线程
  2. 公平模式下,会去看排队的队列是否有更早的线程在排队获取
  3. 非公平模式下,不会去检查排队队列

释放许可:acquire()

默认释放一个许可

  1. public void release() {
  2. sync.releaseShared(1); // 释放一个许可
  3. }

调用的还是AQS框架里面的代码实现:

  1. public final boolean releaseShared(int arg) {
  2. if (tryReleaseShared(arg)) { // tryReleaseShared 是信号量自己实现的
  3. doReleaseShared();
  4. return true;
  5. }
  6. return false;
  7. }

tryReleaseShared 方法实现:

说明一下,这个释放许可的实现,公平模式和非公平模式都是调用的同一个实现。

  1. protected final boolean tryReleaseShared(int releases) {
  2. for (;;) { // 自旋
  3. int current = getState(); //当前可用的许可
  4. int next = current + releases; // 加上释放的许可
  5. if (next < current) // 以防你传个负的树过来释放
  6. throw new Error("Maximum permit count exceeded");
  7. if (compareAndSetState(current, next)) // CAS 修改,成功就是释放成功,失败的话继续自旋
  8. return true;
  9. }
  10. }

释放许可总结:

  1. 释放许可就是把开始获得的许可还回去
  2. 用到CAS来修改许可证数,用自旋来保证一定会还回去(直到还成功为止)

其他API方法

Semaphore 还有其他的很多API可以调用,但其实源码都差不多,所以这里就不继续分析了,如果你把我之前分析AQS、ReentrantLock、ReentrantReadWriteLock的源码文章也看了,你就会发现这个Semaphore 的源码读起来非常简单了,这里再简单说下其他API的作用。

  1. void acquire(int permits)

    和上面分析的acquire()功能一样,只不过你可以指定获取许可数,源码在减的时候就不是减1了,在释放的时候也要注意,最好保持一致。

    被中断会抛出异常
  2. void acquireUninterruptibly()

    Uninterruptibly(),和 acquire() 方法的唯一区别就是线程被中断了也不会抛出异常,其他完全一致
  3. void acquireUninterruptibly(int permits)

    被中断不抛出异常,指定每次获取许可的数量
  4. boolean tryAcquire()

    只会尝试一次获得许可,获得成功了就返回true,失败了不会去排队阻塞线程。

    还有几个带参数的,意思都差不多。
  5. int availablePermits()

    返回可用的许可数
  6. void release(int permits)

    一次释放指定的许可数

Semaphore 总结

  1. Semaphore 也是基于AQS框架来实现的
  2. Semaphore 也有公平和非公平之说,公平就是在获取许可之前会先看一下队列是否有其他线程在排队
  3. Semaphore 的初始信号量必须指定,如果是1的话,功能就相当于一个互斥锁了
  4. Semaphore 支持重入获得许可,但是这里要注意的是,如果一个线程先获得了许可,没释放又来获得许可,这时候许可数不足的情况下,当前线程会被阻塞,有可能会死锁。
  5. 如果这篇文章没看懂,可以先去看看的之前关于AQS(AQS分析文章里面有一个自己实现的共享锁,和这里的信号量非常相似)、ReentrantLock和RRWLock源码分析的文章,所有文章看完,保证你一懂百懂,奥利给。

源码分析:Semaphore之信号量的更多相关文章

  1. 【JDK】JDK源码分析-Semaphore

    概述 Semaphore 是并发包中的一个工具类,可理解为信号量.通常可以作为限流器使用,即限制访问某个资源的线程个数,比如用于限制连接池的连接数. 打个通俗的比方,可以把 Semaphore 理解为 ...

  2. 【JUC】JDK1.8源码分析之Semaphore(六)

    一.前言 分析了CountDownLatch源码后,下面接着分析Semaphore的源码.Semaphore称为计数信号量,它允许n个任务同时访问某个资源,可以将信号量看做是在向外分发使用资源的许可证 ...

  3. Java并发包中Semaphore的工作原理、源码分析及使用示例

    1. 信号量Semaphore的介绍 我们以一个停车场运作为例来说明信号量的作用.假设停车场只有三个车位,一开始三个车位都是空的.这时如果同时来了三辆车,看门人允许其中它们进入进入,然后放下车拦.以后 ...

  4. Semaphore 源码分析

    Semaphore 源码分析 1. 在阅读源码时做了大量的注释,并且做了一些测试分析源码内的执行流程,由于博客篇幅有限,并且代码阅读起来没有 IDE 方便,所以在 github 上提供JDK1.8 的 ...

  5. 并发编程之 Semaphore 源码分析

    前言 并发 JUC 包提供了很多工具类,比如之前说的 CountDownLatch,CyclicBarrier ,今天说说这个 Semaphore--信号量,关于他的使用请查看往期文章并发编程之 线程 ...

  6. Java - "JUC" Semaphore源码分析

    Java多线程系列--“JUC锁”11之 Semaphore信号量的原理和示例 Semaphore简介 Semaphore是一个计数信号量,它的本质是一个"共享锁". 信号量维护了 ...

  7. 鸿蒙内核源码分析(信号量篇) | 谁在负责解决任务的同步 | 百篇博客分析OpenHarmony源码 | v29.01

    百篇博客系列篇.本篇为: v29.xx 鸿蒙内核源码分析(信号量篇) | 谁在负责解决任务的同步 | 51.c.h .o 进程通讯相关篇为: v26.xx 鸿蒙内核源码分析(自旋锁篇) | 自旋锁当立 ...

  8. 并发编程(七)——AbstractQueuedSynchronizer 之 CountDownLatch、CyclicBarrier、Semaphore 源码分析

    这篇,我们的关注点是 AQS 最后的部分,共享模式的使用.本文先用 CountDownLatch 将共享模式说清楚,然后顺着把其他 AQS 相关的类 CyclicBarrier.Semaphore 的 ...

  9. Spring AMQP 源码分析 02 - CachingConnectionFactory

    ### 准备 ## 目标 了解 CachingConnectionFactory 在默认缓存模式下的工作原理   ## 前置知识   <Spring AMQP 源码分析 01 - Impatie ...

随机推荐

  1. Ubuntu20.04 体验和美化

    Ubuntu20.04美化和体验 windows用久了,换下系统也挺好的.ubuntu20.04优化后,用起来蛮舒服的. 系统配置 1.修改软件源 Ubuntu默认是国外的软件源, 我们可以手动切换为 ...

  2. go 接口实现

    package main import ( "fmt" ) // 定义接口 type Beahavior interface { Run() string Eat(thing st ...

  3. nginx集群:nginx配置负载均衡集群(nginx1.18.0)

    一,nginx的负载均衡集群的特点: 1,nginx集群和lvs的不同? lvs集群:工作在第4层(传输层) nginx集群:工作在第7层(应用层) lvs集群:性能更强 nginx集群:功能更强:可 ...

  4. .NET Core开源任务调度平台ScheduleMaster上新了

    ScheduleMaster上一次比较大的更新还是在6月份,转眼已经快过去4个月了,这段时间比较忙,中间只更新过一次修复了几个小bug.要总结这次更新的话,必须要用"千呼万唤始出来" ...

  5. C++switch结构

  6. 【事件中心 Azure Event Hub】在Linux环境中(Ubuntu)安装Logstash的简易步骤及配置连接到Event Hub

    在文章([事件中心 Azure Event Hub]使用Logstash消费EventHub中的event时遇见的几种异常(TimeoutException, ReceiverDisconnected ...

  7. SQL 禁止在 .NET Framework 中执行用户代码。启用 "clr enabled" 配置选项

    注:本文摘自:http://blog.csdn.net/heshengfen123/article/details/3597125 在执行SQL脚本过程中如果出现 禁止在 .NET Framework ...

  8. 习题解答chapter04

    题目: 实验:利用IDE的debug功能给例6.4和例6.6的new语句设置断点,使用单步调试(step into/step over)跟踪子类对象实例化(初始化)的执行顺序,并总结该过程.(教材:J ...

  9. javaweb学习笔记整理补课

    javaweb学习笔记整理补课 * JavaWeb: * 使用Java语言开发基于互联网的项目 * 软件架构: 1. C/S: Client/Server 客户端/服务器端 * 在用户本地有一个客户端 ...

  10. filezilla pureftpd 读取目录列表失败

    放行   21, 39000 - 40000端口