源码分析:Semaphore之信号量
简介
Semaphore 又名计数信号量,从概念上来讲,信号量初始并维护一定数量的许可证,使用之前先要先获得一个许可,用完之后再释放一个许可。信号量通常用于限制线程的数量来控制访问某些资源,从而达到单机限流的目的,比如SpringCloud 中的Zuul 组件用的是 Hystrix 的信号量(semaphore)隔离模式。
源码分析
重要的内部类
Semaphore 和 ReentrantLock 内部类完全相似, 有3个重要的内部类,分别也是 Sync
、NonfairSync
和FairSync
;
- Sync 是后面两个的父类,继承至AbstractQueuedSynchronizer(AQS)
- NonfairSync和FairSync都继承至Sync
- NonfairSync 主要用于实现非公平锁,FairSync 主要用于实现公平锁
如果你看了前面几天关于锁的源码分析,是不是发现它们的套路都差不多呢?
重要的属性
和 ReentrantLock 也完全一样,只有一个重要的属性,同步器sync:
private final Sync sync;
两个构造方法
// ①指定初始许可证数量
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
// ②指定初始许可证数量和公平模式
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
两个构造方法最后都是初始化许可证数量,调用的也就是同步器里面的构造方法来初始化AQS 里面的state字段
// Sync 的构造方法
Sync(int permits) {
setState(permits);
}
// AQS 中的代码
protected final void setState(int newState) {
state = newState;
}
获取许可:acquire()
默认每次获得1个许可,如果没有可用的许可证会阻塞线程,或者被中断抛出异常。
源码分析:
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1); // 默认每次获得1个许可
}
acquireSharedInterruptibly(1)
会调用 AQS 里面的方法:
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted()) // 线程被中断,抛出异常
throw new InterruptedException();
if (tryAcquireShared(arg) < 0) // tryAcquireShared 尝试获得许可,返回小于0 表示没有获得许可
doAcquireSharedInterruptibly(arg); // 没有获得许可,排队阻塞
}
tryAcquireShared(arg)
方法:
tryAcquireShared 有两种实现,也就是 FairSync(公平模式) 和 NonfairSync(非公平模式) 不同实现。
公平模式的实现代码
FairSync.tryAcquireShared
:// acquires
protected int tryAcquireShared(int acquires) {
for (;;) { // 自旋
if (hasQueuedPredecessors()) // 检查是否有更早的线程在排队获得许可
return -1; // 有排队的线程,返回-1,小于0表示获得许可失败
int available = getState(); // 获得可用许可数
int remaining = available - acquires; // 减去一个许可,计算剩余的许可数
if (remaining < 0 || compareAndSetState(available, remaining))
// remaining < 0 成立的话就说明获取许可失败了,出去也要排队阻塞线程
return remaining;
}
}
非公平模式的实现代码NonfairSync.tryAcquireShared:
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires); // 调用父类Sync里面的实现方法
}
// 父类Sync里面的实现方法
final int nonfairTryAcquireShared(int acquires) {
for (;;) { // 自旋
int available = getState(); // 获得可用许可数
int remaining = available - acquires; // 减去一个许可,计算剩余的许可数
if (remaining < 0 || compareAndSetState(available, remaining))
// remaining < 0 成立的话就说明获取许可失败了,出去也要排队阻塞线程
return remaining;
}
}
有没有发现他们的代码非常相识?公平模式的实现就只是比非公平模式多了一个
hasQueuedPredecessors()
方法调用判断,这个方法主要就是检查排队的队列里面是不是还有其他线程。在之前分析ReentrantLock 源码的文章中也有提到。
如果tryAcquireShared
方法没有获得许可(返回值小于0),就会进入到AQS 的 doAcquireSharedInterruptibly 方法:
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
// 为当前线程创建排队节点,并加入到队列
// addWaiter方法的分析在之前的AQS分析文章已经分析过了
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) { // 获得许可
setHeadAndPropagate(node, r); // 设置排队的头节点
p.next = null; // help GC
failed = false;
return; // 线程获得许可,退出
}
}
// shouldParkAfterFailedAcquire 如果线程应阻塞,则返回true
// 之前的AQS分析文章已经分析过了
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
// 被中断了,抛出异常
throw new InterruptedException();
}
} finally {
if (failed) // 节点被取消
cancelAcquire(node);
}
}
获得许可总结:
- 获得许可就是对初始化的许可证进行减1,直到没有许可证了就会进入到队列排队阻塞线程
- 公平模式下,会去看排队的队列是否有更早的线程在排队获取
- 非公平模式下,不会去检查排队队列
释放许可:acquire()
默认释放一个许可
public void release() {
sync.releaseShared(1); // 释放一个许可
}
调用的还是AQS框架里面的代码实现:
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) { // tryReleaseShared 是信号量自己实现的
doReleaseShared();
return true;
}
return false;
}
tryReleaseShared 方法实现:
说明一下,这个释放许可的实现,公平模式和非公平模式都是调用的同一个实现。
protected final boolean tryReleaseShared(int releases) {
for (;;) { // 自旋
int current = getState(); //当前可用的许可
int next = current + releases; // 加上释放的许可
if (next < current) // 以防你传个负的树过来释放
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next)) // CAS 修改,成功就是释放成功,失败的话继续自旋
return true;
}
}
释放许可总结:
- 释放许可就是把开始获得的许可还回去
- 用到CAS来修改许可证数,用自旋来保证一定会还回去(直到还成功为止)
其他API方法
Semaphore 还有其他的很多API可以调用,但其实源码都差不多,所以这里就不继续分析了,如果你把我之前分析AQS、ReentrantLock、ReentrantReadWriteLock的源码文章也看了,你就会发现这个Semaphore 的源码读起来非常简单了,这里再简单说下其他API的作用。
- void acquire(int permits)
和上面分析的acquire()功能一样,只不过你可以指定获取许可数,源码在减的时候就不是减1了,在释放的时候也要注意,最好保持一致。
被中断会抛出异常 - void acquireUninterruptibly()
Uninterruptibly(),和 acquire() 方法的唯一区别就是线程被中断了也不会抛出异常,其他完全一致 - void acquireUninterruptibly(int permits)
被中断不抛出异常,指定每次获取许可的数量 - boolean tryAcquire()
只会尝试一次获得许可,获得成功了就返回true,失败了不会去排队阻塞线程。
还有几个带参数的,意思都差不多。 - int availablePermits()
返回可用的许可数 - void release(int permits)
一次释放指定的许可数
Semaphore 总结
- Semaphore 也是基于AQS框架来实现的
- Semaphore 也有公平和非公平之说,公平就是在获取许可之前会先看一下队列是否有其他线程在排队
- Semaphore 的初始信号量必须指定,如果是1的话,功能就相当于一个互斥锁了
- Semaphore 支持重入获得许可,但是这里要注意的是,如果一个线程先获得了许可,没释放又来获得许可,这时候许可数不足的情况下,当前线程会被阻塞,有可能会死锁。
- 如果这篇文章没看懂,可以先去看看的之前关于AQS(AQS分析文章里面有一个自己实现的共享锁,和这里的信号量非常相似)、ReentrantLock和RRWLock源码分析的文章,所有文章看完,保证你一懂百懂,奥利给。
源码分析:Semaphore之信号量的更多相关文章
- 【JDK】JDK源码分析-Semaphore
概述 Semaphore 是并发包中的一个工具类,可理解为信号量.通常可以作为限流器使用,即限制访问某个资源的线程个数,比如用于限制连接池的连接数. 打个通俗的比方,可以把 Semaphore 理解为 ...
- 【JUC】JDK1.8源码分析之Semaphore(六)
一.前言 分析了CountDownLatch源码后,下面接着分析Semaphore的源码.Semaphore称为计数信号量,它允许n个任务同时访问某个资源,可以将信号量看做是在向外分发使用资源的许可证 ...
- Java并发包中Semaphore的工作原理、源码分析及使用示例
1. 信号量Semaphore的介绍 我们以一个停车场运作为例来说明信号量的作用.假设停车场只有三个车位,一开始三个车位都是空的.这时如果同时来了三辆车,看门人允许其中它们进入进入,然后放下车拦.以后 ...
- Semaphore 源码分析
Semaphore 源码分析 1. 在阅读源码时做了大量的注释,并且做了一些测试分析源码内的执行流程,由于博客篇幅有限,并且代码阅读起来没有 IDE 方便,所以在 github 上提供JDK1.8 的 ...
- 并发编程之 Semaphore 源码分析
前言 并发 JUC 包提供了很多工具类,比如之前说的 CountDownLatch,CyclicBarrier ,今天说说这个 Semaphore--信号量,关于他的使用请查看往期文章并发编程之 线程 ...
- Java - "JUC" Semaphore源码分析
Java多线程系列--“JUC锁”11之 Semaphore信号量的原理和示例 Semaphore简介 Semaphore是一个计数信号量,它的本质是一个"共享锁". 信号量维护了 ...
- 鸿蒙内核源码分析(信号量篇) | 谁在负责解决任务的同步 | 百篇博客分析OpenHarmony源码 | v29.01
百篇博客系列篇.本篇为: v29.xx 鸿蒙内核源码分析(信号量篇) | 谁在负责解决任务的同步 | 51.c.h .o 进程通讯相关篇为: v26.xx 鸿蒙内核源码分析(自旋锁篇) | 自旋锁当立 ...
- 并发编程(七)——AbstractQueuedSynchronizer 之 CountDownLatch、CyclicBarrier、Semaphore 源码分析
这篇,我们的关注点是 AQS 最后的部分,共享模式的使用.本文先用 CountDownLatch 将共享模式说清楚,然后顺着把其他 AQS 相关的类 CyclicBarrier.Semaphore 的 ...
- Spring AMQP 源码分析 02 - CachingConnectionFactory
### 准备 ## 目标 了解 CachingConnectionFactory 在默认缓存模式下的工作原理 ## 前置知识 <Spring AMQP 源码分析 01 - Impatie ...
随机推荐
- js实现自定义弹窗
众所周知,浏览器自带的原生弹窗很不美观,而且功能比较单一,绝大部分时候我们都会按照设计图自定义弹窗或者直接使用注入layer的弹窗等等.前段时间在慕课网上看到了一个自定义弹窗的实现,自己顺便就学习尝试 ...
- nginx安全: 配置http基本验证(Basic Auth)(nginx 1.18.0)
一,http基本验证的作用: 1,http基本身份验证会从浏览器弹出登录窗口, 简单明了,容易理解, 对于面向终端用户的前台来说,不够友好, 但对于内部员工操作的后台还是很有用,通常作为一层安全措施应 ...
- mac 安装appium
mocOS 10.15.5 开启方式:设置->默认编辑器->Markdown编辑器
- js后台提交成功后 关闭当前页 并刷新父窗体
后台提交成功后 关闭当前页 并刷新父窗体 this.ClientScript.RegisterStartupScript(this.GetType(), "message", &q ...
- JDBC Statement PrepareStatement
1.JDBC中Statement接口和PrepareStatement接口关系与区别 Statement接口不能使用占位符?,需要拼sql,所以没有setInt,setString等方法:Prepar ...
- [bug]录
后端请求地址找不到,Tomcat服务器无响应 报404错误 查看资料和视频,用了2天时间(实际不到2天),还没有解决,我在书上找了描述,也问了别人,路径改成图上所说,还是未解决,找资源找到不广泛,没找 ...
- 结对项目的PS表格
PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟) Planning 计划 60 80 Estimate 估计这个任务需要多少时间 10 ...
- R语言学习-(金融数据获取和简单的分析)
利用R语言中的quantmod包和fBasics对股票数据的获取和简要的分析, 通过获取的数据进行典型图像绘制,使用JB正态性检验来检验是否服从于正态分布. 前提概要:quantmod 包默认是访问 ...
- 上午小测1 B.序列 哈希表+数学
题目描述 \(EZ\) 每周一都要举行升旗仪式,国旗班会站成一整列整齐地向前行进. 郭神摄像师想要选取其中一段照下来.他想让这一段中每个人的身高成等比数列,展示出最萌身高差.但他发现这个太难办到了.于 ...
- STM32入门系列-库目录及文件介绍
已经介绍了过了CMSIS标准,ST公司按照这个标准设计了一套基于STM32F10x的固件库,我们可以直接在ST公司的官网进行下载,现在给大家STM32最新固件库v3.5,在网盘上给大家提供了下载包,链 ...