Java并发包源码学习系列:同步组件Semaphore源码解析
- Semaphore概述及案例学习
- 类图结构及重要字段
- void acquire()
- void acquire(int permits)
- void acquireUninterruptibly()
- void acquireUninterruptibly(int permits)
- boolean tryAcquire()
- boolean tryAcquire(int permits)
- boolean tryAcquire(int permits, long timeout, TimeUnit unit)
- void release()
- void release(int permits)
- 其他方法
- 总结
- 参考阅读
Semaphore概述及案例学习
Semaphore信号量用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理地使用公共资源。
public class SemaphoreTest {
private static final int THREAD_COUNT = 30;
private static ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_COUNT);
private static Semaphore s = new Semaphore(10); //10个许可证数量,最大并发数为10
public static void main(String[] args) {
for(int i = 0; i < THREAD_COUNT; i ++){ //执行30个线程
threadPool.execute(new Runnable() {
@Override
public void run() {
s.tryAcquire(); //尝试获取一个许可证
System.out.println("save data");
s.release(); //使用完之后归还许可证
}
});
}
threadPool.shutdown();
}
}
- 创建一个大小为30的线程池,但是信号量规定在10,保证许可证数量为10。
- 每次线程调用
tryAcquire()或者acquire()方法都会原子性的递减许可证的数量,release()会原子性递增许可证数量。
类图结构及重要字段

public class Semaphore implements java.io.Serializable {
private static final long serialVersionUID = -3222578661600680210L;
/** All mechanics via AbstractQueuedSynchronizer subclass */
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {
// permits指定初始化信号量个数
Sync(int permits) {
setState(permits);
}
// ...
}
static final class NonfairSync extends Sync {...}
static final class FairSync extends Sync {...}
// 默认采用非公平策略
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
// 可以指定公平策略
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
//...
}
- 基于AQS,类似于ReentrantLock,Sync继承自AQS,有公平策略和非公平策略两种实现。
- 类似于CountDownLatch,state在这里也是通过构造器指定,表示初始化信号量的个数。
本篇文章阅读需要建立在一定的AQS基础之上,这边推荐几篇前置文章,可以瞅一眼:
void acquire()
调用该方法时,表示希望获取一个信号量资源,相当于acquire(1)。
如果当前信号量个数大于0,CAS将当前信号量值减1,成功后直接返回。
如果当前信号量个数等于0,则当前线程将被置入AQS的阻塞队列。
该方法是响应中断的,其他线程调用了该线程的interrupt()方法,将会抛出中断异常返回。
// Semaphore.java
public void acquire() throws InterruptedException {
// 传递的 arg 为 1 , 获取1个信号量资源
sync.acquireSharedInterruptibly(1);
}
// AQS.java
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
// 线程被 中断, 抛出中断异常
if (Thread.interrupted())
throw new InterruptedException();
// 子类实现, 公平和非公平两种策略
if (tryAcquireShared(arg) < 0)
// 如果获取失败, 则置入阻塞队列,
// 再次进行尝试, 尝试失败则挂起当前线程
doAcquireSharedInterruptibly(arg);
}
非公平
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -2694183684443567898L;
NonfairSync(int permits) {
super(permits);
}
protected int tryAcquireShared(int acquires) {
// 这里直接调用Sync定义的 非公平共享模式获取方法
return nonfairTryAcquireShared(acquires);
}
}
abstract static class Sync extends AbstractQueuedSynchronizer {
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
// 获取当前信号量的值
int available = getState();
// 减去需要获取的值, 得到剩余的信号量个数
int remaining = available - acquires;
// 不剩了,表示当前信号量个数不能满足需求, 返回负数, 线程置入AQS阻塞
// 还有的剩, CAS设置当前信号量值为剩余值, 并返回剩余值
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
}
你会发现,非公平策略是无法保证【AQS队列中阻塞的线程】和【当前线程】获取的顺序的,当前线程是有可能在排队的线程之前就拿到资源,产生插队现象。
公平策略就不一样了,它会通过hasQueuedPredecessors()方法看看队列中是否存在前驱节点,以保证公平性。
公平策略
static final class FairSync extends Sync {
private static final long serialVersionUID = 2014338818796000944L;
FairSync(int permits) {
super(permits);
}
protected int tryAcquireShared(int acquires) {
for (;;) {
// 如果队列中在此之前已经有线程在排队了,直接放弃获取
if (hasQueuedPredecessors())
return -1;
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
}
void acquire(int permits)
在acquire()的基础上,指定了获取信号量的数量permits。
public void acquire(int permits) throws InterruptedException {
if (permits < 0) throw new IllegalArgumentException();
sync.acquireSharedInterruptibly(permits);
}
void acquireUninterruptibly()
该方法与acquire()类似,但是不响应中断。
public void acquireUninterruptibly() {
sync.acquireShared(1);
}
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
void acquireUninterruptibly(int permits)
该方法与acquire(permits)类似,但是不响应中断。
public void acquireUninterruptibly(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.acquireShared(permits);
}
boolean tryAcquire()
tryAcquire和acquire非公平策略公用一个逻辑,但是区别在于,如果获取信号量失败,或者CAS失败,将会直接返回false,而不会置入阻塞队列中。
一般try开头的方法的特点就是这样,尝试一下,成功是最好,失败也不至于被阻塞,而是立刻返回false。
public boolean tryAcquire() {
return sync.nonfairTryAcquireShared(1) >= 0;
}
abstract static class Sync extends AbstractQueuedSynchronizer {
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
}
boolean tryAcquire(int permits)
相比于普通的tryAcquire(),指定了permits的值。
public boolean tryAcquire(int permits) {
if (permits < 0) throw new IllegalArgumentException();
return sync.nonfairTryAcquireShared(permits) >= 0;
}
boolean tryAcquire(int permits, long timeout, TimeUnit unit)
相比于tryAcquire(int permits),增加了超时控制。
public boolean tryAcquire(int permits, long timeout, TimeUnit unit)
throws InterruptedException {
if (permits < 0) throw new IllegalArgumentException();
return sync.tryAcquireSharedNanos(permits, unit.toNanos(timeout));
}
void release()
将信号量值加1,如果有线程因为调用acquire方法而被阻塞在AQS阻塞队列中,将根据公平策略选择一个信号量个数满足需求的线程唤醒,线程唤醒后也会尝试获取新增的信号量。
参考文章:Java并发包源码学习系列:AQS共享模式获取与释放资源
// Semaphore.java
public void release() {
sync.releaseShared(1);
}
// AQS.java
public final boolean releaseShared(int arg) {
// 尝试释放锁
if (tryReleaseShared(arg)) {
// 释放成功, 唤醒AQS队列里面最先挂起的线程
// https://blog.csdn.net/Sky_QiaoBa_Sum/article/details/112386838
doReleaseShared();
return true;
}
return false;
}
// Semaphore#Sync.java
abstract static class Sync extends AbstractQueuedSynchronizer {
protected final boolean tryReleaseShared(int releases) {
for (;;) {
// 获取当前信号量
int current = getState();
// 期望加上releases
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
// CAS操作,更新
if (compareAndSetState(current, next))
return true;
}
}
}
void release(int permits)
和release()相比指定了permits的值。
public void release(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.releaseShared(permits);
}
其他方法
Semaphore还提供其他一些方法,实现比较简单,这边就简单写一下吧:
// 返回此信号量中当前可用的许可证数量, 其实就是得到当前的 state值 getState()
public int availablePermits() {
return sync.getPermits();
}
// 将state更新为0, 返回0
public int drainPermits() {
return sync.drainPermits();
}
// 减少reduction个许可证
protected void reducePermits(int reduction) {
if (reduction < 0) throw new IllegalArgumentException();
sync.reducePermits(reduction);
}
// 判断公平策略
public boolean isFair() {
return sync instanceof FairSync;
}
// 判断是否有线程证在等待获取许可证
public final boolean hasQueuedThreads() {
return sync.hasQueuedThreads();
}
// 返回正在等待获取许可证的线程数
public final int getQueueLength() {
return sync.getQueueLength();
}
// 返回所有等待获取许可证的线程集合
protected Collection<Thread> getQueuedThreads() {
return sync.getQueuedThreads();
}
总结
Semaphore信号量用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理地使用公共资源。
- 基于AQS,类似于ReentrantLock,Sync继承自AQS,有公平策略和非公平策略两种实现。
- 类似于CountDownLatch,state在这里也是通过构造器指定,表示初始化信号量的个数。
每次线程调用tryAcquire()或者acquire()方法都会原子性的递减许可证的数量,release()会原子性递增许可证数量,只要有许可证就可以重复使用。
参考阅读
- 《Java并发编程之美》
- 《Java并发编程的艺术》
Java并发包源码学习系列:同步组件Semaphore源码解析的更多相关文章
- Java并发包源码学习系列:CLH同步队列及同步资源获取与释放
目录 本篇学习目标 CLH队列的结构 资源获取 入队Node addWaiter(Node mode) 不断尝试Node enq(final Node node) boolean acquireQue ...
- Java并发包源码学习系列:同步组件CountDownLatch源码解析
目录 CountDownLatch概述 使用案例与基本思路 类图与基本结构 void await() boolean await(long timeout, TimeUnit unit) void c ...
- Java并发包源码学习系列:同步组件CyclicBarrier源码解析
目录 CyclicBarrier概述 案例学习 类图结构及重要字段 内部类Generation及相关方法 void reset() void breakBarrier() void nextGener ...
- Java并发包源码学习系列:AQS共享式与独占式获取与释放资源的区别
目录 Java并发包源码学习系列:AQS共享模式获取与释放资源 独占式获取资源 void acquire(int arg) boolean acquireQueued(Node, int) 独占式释放 ...
- Java并发包源码学习系列:ReentrantLock可重入独占锁详解
目录 基本用法介绍 继承体系 构造方法 state状态表示 获取锁 void lock()方法 NonfairSync FairSync 公平与非公平策略的差异 void lockInterrupti ...
- Java并发包源码学习系列:ReentrantReadWriteLock读写锁解析
目录 ReadWriteLock读写锁概述 读写锁案例 ReentrantReadWriteLock架构总览 Sync重要字段及内部类表示 写锁的获取 void lock() boolean writ ...
- Java并发包源码学习系列:详解Condition条件队列、signal和await
目录 Condition接口 AQS条件变量的支持之ConditionObject内部类 回顾AQS中的Node void await() 添加到条件队列 Node addConditionWaite ...
- Java并发包源码学习系列:挂起与唤醒线程LockSupport工具类
目录 LockSupport概述 park与unpark相关方法 中断演示 blocker的作用 测试无blocker 测试带blocker JDK提供的demo 总结 参考阅读 系列传送门: Jav ...
- Java并发包源码学习系列:JDK1.8的ConcurrentHashMap源码解析
目录 为什么要使用ConcurrentHashMap? ConcurrentHashMap的结构特点 Java8之前 Java8之后 基本常量 重要成员变量 构造方法 tableSizeFor put ...
随机推荐
- ubuntu虚拟机重启后进入initramfs的解决办法
问题:windows下使用VMware或者自己安装的ubuntu系统出现,不能正常进入系统,而是进入一个以initramfs开头的命令行界面! 原因:不正常的关闭系统,导致系统文件损坏,/dev/sd ...
- 区域之间PA、FortiGate内网互联专线、IPSec互备方案
网络拓扑: 现状描述: 办公区和IDC之间有防火墙互联的点对点专线实现内网互通,现需要两地防火墙构建IPSec VPN作为专线的热备链路. 解决方案: 飞塔侧 1.按照阶段一.阶段二建立IPSec V ...
- 枚举--让盗版美国总统wcc给你整明白哈哈
1.为什么要有枚举 Java中的枚举其实是一种语法糖,在 JDK 1.5之后出现,用来表示固定且有限个的对象.比如一个季节类有春.夏.秋.冬四个对象:一个星期有星期一到星期日七个对象.这些明显都是固定 ...
- FunnyXEN
For any positive integer n, we define function F(n) and XEN(n).For a collection S(n)={1,2,...,2n}, w ...
- Preliminaries for Benelux Algorithm Programming Contest 2019
A. Architecture 如果行最大值中的最大值和列最大值中的最大值不同的话,那么一定会产生矛盾,可以手模一个样例看看. 当满足行列最大值相同条件的时候,就可以判定了. 因为其余的地方一定可以构 ...
- Codeforces Round #613 (Div. 2) A. Mezo Playing Zoma(逻辑)
题意: 给出一个移动序列,可以无效化一些指令,问可以移动到多少不同位置. 思路: 第一印象是统计左右指令数目,后来发现左右指令数目和即字符串长度. #include <bits/stdc++.h ...
- F - Count the Colors(线段树)
Painting some colored segments on a line, some previously painted segments may be covered by some th ...
- Codeforces Round #544 (Div. 3) E. K Balanced Teams (DP)
题意:有\(n\)个人,每个人的能力值是\(a_i\),现在你想将这些人分成\(k\)组(没必要全选),但是每组中最高水平和最低水平的人的能力差值必须\(\le 5\),问最多能选多少人. 题解:想了 ...
- hdu1828 Picture(线段树+扫描线+矩形周长)
看这篇博客前可以看一下扫描线求面积:线段树扫描线(一.Atlantis HDU - 1542(覆盖面积) 二.覆盖的面积 HDU - 1255(重叠两次的面积)) 解法一·:两次扫描线 如图我们可以 ...
- Codeforces Round #668 (Div. 2) B. Array Cancellation (思维,贪心)
题意:有一个长度为\(n\)并且所有元素和为\(0\)的序列,你可以使\(a_{i}-1\)并且\(a_{j}+1\),如果\(i<j\),那么这步操作就是免费的,否则需要花费一次操作,问最少操 ...