【JDK】JDK源码分析-Semaphore
概述
Semaphore 是并发包中的一个工具类,可理解为信号量。通常可以作为限流器使用,即限制访问某个资源的线程个数,比如用于限制连接池的连接数。
打个通俗的比方,可以把 Semaphore 理解为一辆公交车:车上的座位数(初始的“许可” permits 数量)是固定的,行驶期间如果有人上车(获取许可),座位数(许可数量)就会减少,当人满的时候不能再继续上车了(获取许可失败);而有人下车(释放许可)后就空出了一些座位,其他人就可以继续上车了。
下面具体分析其代码实现。
代码分析
Semaphore 的方法如下:

其中主要方法是 acquire() 和 release() 相关的一系列方法,它们的作用类似。我们先从构造器开始分析。
构造器
private final Sync sync; // 初始化 Semaphore,传入指定的许可数量,非公平
public Semaphore(int permits) {
sync = new NonfairSync(permits);
} // 初始化 Semaphore,传入指定的许可数量,指定是否公平
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
构造器初始化了 Sync 变量,根据传入的 fair 值指定为 FairSync 或 NonFairSync,下面分析这三个类。
内部嵌套类 Sync:
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 1192457210091910933L;
// 构造器,将父类 AQS 的 state 变量初始化为给定的 permits
Sync(int permits) {
setState(permits);
}
// 非公平方式尝试获取许可(减少 state 的值)
final int nonfairTryAcquireShared(int acquires) {
// 自旋操作
for (;;) {
// 获取许可值(state),并尝试 CAS 修改为减去后的结果
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
// 释放许可(增加 state 的值)
protected final boolean tryReleaseShared(int releases) {
for (;;) {
// 操作与获取类似,不同的在于此处是增加 state 值
int current = getState();
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
// 一些方法未给出...
}
可以看到 Sync 类继承自 AQS,并重写了 AQS 的 tryReleaseShared 方法,其中获取和释放许可分别对应的是对 AQS 中 state 值的减法和加法操作。具体可参考前文对 AQS 共享模式的分析「JDK源码分析-AbstractQueuedSynchronizer(3)」。
NonFairSync (非公平版本实现):
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -2694183684443567898L;
// 调用父类 Sync 的构造器来实现
NonfairSync(int permits) {
super(permits);
}
// 重写 AQS 的 tryAcquireShared 方法,代码实现在父类 Sync 中
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
}
FairSync (公平版本实现):
static final class FairSync extends Sync {
private static final long serialVersionUID = 2014338818796000944L;
// 构造器调用父类 Sync 的构造器来实现
FairSync(int permits) {
super(permits);
}
// 重写 AQS 的 tryAcquireShared 方法,尝试获取许可(permit)
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;
}
}
}
PS: 体现“公平”的地方在于 tryAcquireShared 方法中,公平的版本会先判断队列中是否有其它线程在等待(hasQueuedPredecessors 方法)。
主要方法的代码实现:
// 获取一个许可(可中断)
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
} // 获取一个许可(不响应中断)
public void acquireUninterruptibly() {
sync.acquireShared(1);
} // 尝试获取一个许可
public boolean tryAcquire() {
return sync.nonfairTryAcquireShared(1) >= 0;
} // 尝试获取一个许可(有超时等待)
public boolean tryAcquire(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
} // 释放一个许可
public void release() {
sync.releaseShared(1);
}
还有一系列类似的操作,只不过获取/释放许可的数量可以指定:
// 获取指定数量的许可(可中断)
public void acquire(int permits) throws InterruptedException {
if (permits < 0) throw new IllegalArgumentException();
sync.acquireSharedInterruptibly(permits);
} // 获取指定数量的许可(不可中断)
public void acquireUninterruptibly(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.acquireShared(permits);
} // 尝试获取指定数量的许可
public boolean tryAcquire(int permits) {
if (permits < 0) throw new IllegalArgumentException();
return sync.nonfairTryAcquireShared(permits) >= 0;
} // 尝试获取指定数量的许可(有超时等待)
public boolean tryAcquire(int permits, long timeout, TimeUnit unit)
throws InterruptedException {
if (permits < 0) throw new IllegalArgumentException();
return sync.tryAcquireSharedNanos(permits, unit.toNanos(timeout));
} // 释放指定数量的许可
public void release(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.releaseShared(permits);
}
可以看到,Semaphore 的主要方法都是在嵌套类 FairSync 和 NonFairSync 及其父类 Sync 中实现的,内部嵌套类也是 AQS 的典型用法。
场景举例
为了便于理解 Semaphore 的用法,下面简单举例分析(仅供参考):
public class SemaphoreTest {
public static void main(String[] args) {
// 初始化 Semaphore
// 这里的许可数为 2,即同时最多有 2 个线程可以获取到
Semaphore semaphore = new Semaphore(2);
for (int i = 0; i < 50; i++) {
new Thread(() -> {
try {
// 获取许可
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " 正在执行..");
// 模拟操作
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放许可
semaphore.release();
}
}).start();
}
}
}
/* 执行结果(仅供参考):
Thread-0 正在执行..
Thread-1 正在执行..
Thread-2 正在执行..
Thread-3 正在执行..
...
*/
这里把 Semaphore 的初始许可值设为 2,表示最多有两个线程可同时获取到许可(运行程序可发现线程是两两一起执行的)。设置为其他值也是类似的。
比较特殊的是,如果把 Semaphore 的初始许可值设为 1,可以当做“互斥锁”来使用。
小结
Semaphore 是并发包中的一个工具类,其内部是基于 AQS 共享模式实现的。通常可以作为限流器使用,比如限定连接池等的大小。
相关阅读:
JDK源码分析-AbstractQueuedSynchronizer(3)
Stay hungry, stay foolish.

PS: 本文首发于微信公众号【WriteOnRead】。
【JDK】JDK源码分析-Semaphore的更多相关文章
- JDK Collection 源码分析(2)—— List
JDK List源码分析 List接口定义了有序集合(序列).在Collection的基础上,增加了可以通过下标索引访问,以及线性查找等功能. 整体类结构 1.AbstractList 该类作为L ...
- JDK AtomicInteger 源码分析
@(JDK)[AtomicInteger] JDK AtomicInteger 源码分析 Unsafe 实例化 Unsafe在创建实例的时候,不能仅仅通过new Unsafe()或者Unsafe.ge ...
- 设计模式(十八)——观察者模式(JDK Observable源码分析)
1 天气预报项目需求,具体要求如下: 1) 气象站可以将每天测量到的温度,湿度,气压等等以公告的形式发布出去(比如发布到自己的网站或第三方). 2) 需要设计开放型 API,便于其他第三方也能接入气象 ...
- JDK Collection 源码分析(3)—— Queue
@(JDK)[Queue] JDK Queue Queue:队列接口,对于数据的存取,提供了两种方式,一种失败会抛出异常,另一种则返回null或者false. 抛出异常的接口:add,remove ...
- JDK Collection 源码分析(1)—— Collection
JDK Collection JDK Collection作为一个最顶层的接口(root interface),JDK并不提供该接口的直接实现,而是通过更加具体的子接口(sub interface ...
- JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue
JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue 目的:本文通过分析JDK源码来对比ArrayBlockingQueue 和LinkedBlocki ...
- JDK 源码分析(4)—— HashMap/LinkedHashMap/Hashtable
JDK 源码分析(4)-- HashMap/LinkedHashMap/Hashtable HashMap HashMap采用的是哈希算法+链表冲突解决,table的大小永远为2次幂,因为在初始化的时 ...
- JDK源码分析(11)之 BlockingQueue 相关
本文将主要结合源码对 JDK 中的阻塞队列进行分析,并比较其各自的特点: 一.BlockingQueue 概述 说到阻塞队列想到的第一个应用场景可能就是生产者消费者模式了,如图所示: 根据上图所示,明 ...
- JDK源码分析(1)之 String 相关
在此之前有无数次下定决心要把JDK的源码大致看一遍,但是每次还没点开就已被一个超链接或者其他事情吸引直接跳开了.直到最近突然意识到,因为对源码的了解不深导致踩了许多莫名其妙的坑,所以再次下定决心要把 ...
随机推荐
- Qt 延时处理的几种办法
有些时候,我们需要程序延时一会儿: 这里提供四种方法: 1.多线程程序使用QThread::sleep()或者QThread::msleep()或QThread::usleep()或QThread:: ...
- 关于unicode汉字范围正则表达式的写法
\u2E80-\u2EFF:CJK部首补充: \u2F00-\u2FDF:康熙部首: \u3000-\u303F:CJK标点符号: \u31C0-\u31EF:CJK笔划: \u3200-\u32FF ...
- Kafka与RabbitMQ、ActiveMQ协议区别
对于Kafka与RabbitMQ.ActiveMQ协议,它们具体的区别如下: activemq: activemq支持主从复制.集群.但是集群功能看起来很弱,只有failover功能,即 ...
- 吴裕雄--天生自然 R语言开发学习:基本图形(续三)
#---------------------------------------------------------------# # R in Action (2nd ed): Chapter 6 ...
- Atom配置(VIM党) · iuunhao
为什么说是Vim党呢?首先我是一个深度的Vim用户,自己的电脑上基本上可以兼容Vim的插件都有,所有浏览器,所有编辑器都是Vim的操作方式,当然包括我现在书写的markdown的软件EME也是兼容的V ...
- Vue内置组件keep-alive的使用
本文主要介绍Vue内置组件keep-alive的使用. Vue内置组件keep-alive的使用 keep-alive接收三个props:●include - 字符串或正则表达式.只有名称匹配的组件会 ...
- vuejs 踩坑及经验总结
问题描述 在使用 v-for repeat 组件时控制台会出现警告: 解决方法 在组件标签上使用 v-for : 加 :key 使用 template 标签包裹该组件,再在 template 标签 上 ...
- Pro SQL Server Internal (Dmitri Korotkev)电子书翻译P8-14(12w)
数据行与数据列 数据库的控件逻辑上分成8KB的页,这些页从0开始,连续排序,对特定的文件ID和页码有借鉴意义.页码编号一定是连续的,当SQL服务器中的数据库文件增加时,新的数据页从最高的页码开始编码. ...
- Filter过滤器Listener:监听器
Filter过滤器 1. 概念 web中的过滤器:当访问服务器资源是,过滤器可以将请求拦截下来,完成一些特殊功能: 过滤器的作用 完成通用的操作:例如登录验证.统一编码处理.敏感字符过滤... 快速入 ...
- iMX287A嵌入式Qt环境搭建
目录 1.嵌入式Qt简介 2.查看开发板Qt库的版本 3.第一个嵌入式Qt程序--Hello World 4.Linux桌面版本Qt环境的搭建 5.注意 @ 1.嵌入式Qt简介 Qt 是一个跨平台的应 ...