源码分析: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 ...
随机推荐
- git merge 与 git rebase的区别?
一,git merge 与 git rebase的区别 1,git merge 例如: master分支合并dev分支,git将两个分支dev和master上的所有commit , 按照提交时间的先后 ...
- VS2010下python3的配置
最近突然又想学python,但用惯了vs2010后,十分希望能在vs2010中编译python的程序,于是,秉承着不作到死就不死心的原则就开始了我的配置之旅.但事实上并不难哦?.... 1.首先上场的 ...
- kettle学习笔记(三)— 定时任务的脚本执行
kettle-定时任务 Kettle 的定时任务可以用kettle中的job工作来定时转换(缺点窗口不可关闭),同时也可以使用bat脚本来启动kettle的 '.ktr'转换. 注:这里对定时任务的时 ...
- 文件开启关闭操作c语言模板
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #include <stri ...
- The path "" is not a valid path to the 3.10.0-957.el7.x86_64 kernel headers.
安装 kernel-devel yum install kernel-devel-$(uname -r)
- ORA-12609报错分析
问题:监控不断告警ORA-12609 Wed 10/14/2020 10:40 AM 12CRAC1-ALERT中出现ORA错误,请检查 171- nt OS err code: 0 172- Cli ...
- 一起学Vue:入门
Why-为什么需要Vue? 前端开发存在的问题? 其一,需求变化频率更高 产品功能迭代前端肯定需要跟着调整. 提意见的人多,前端嘛谁都能看得见,所以,谁都可以指手画脚提一点意见.产品经理.项目经理.老 ...
- JUC---12深入理解CAS
一.什么是CAS Compare and Swap, 翻译成比较并交换,是java.util.concurrent.atomic包下的类里面的CompareAndSet()方法:java.util.c ...
- PLC模拟量采集模块在工控领域的应用
在工业现场中,往往需要对温度.电流.电压等模拟量进行控制采集,这可以使用PLC对这些数据进行采集,但是如今生产各种PLC模拟量采集模块的厂家非常多,不同类型的PLC都是有自己专属的模拟量采集模块的,不 ...
- uniapp请求方法的封装
之前在接触uniapp做小程序项目时候,因为不太熟悉,遇到了不少尴尬的时刻,请求方法的封装算是灵魂啊有木有,今天看到有人问题,就把我自己写的发出来让大家参考一下吧. 请求方法的封装我一般用的是prom ...