前言
Semaphore 用来限制能同时访问共享资源的线程上限。它的底层大量用到了 CAS 和 AQS。下面,笔者将带领大家阅读通过加锁解锁流程来阅读 Semaphore 的源码。此源码基于 JDK8 来查看,带大家深入浅出的看懂源码
加锁解锁流程
Semaphore 像一个停车场,permits 就好像停车位数量,当线程获得了 permits 就好比获得了一个停车位,然后停车场显示空余车位减一
刚开始,permits (state) 为 3,这时 5 个线程来获取资源
假设其中 Thread-1、Thread-2、Thread-4 CAS 竞争成功,而 Thread-0 和 Thread-3 竞争失败,进入 AQS 队列 park 阻塞
这时,如果 Thread-4 释放了 permits,则状态如下
接下来 Thread-0 竞争成功,permits 再次设置为 0,设置自己为 head 节点,断开原来的 head 节点,unpark 接下来的 Thread-3 节点,但由于 permits 是 0,因此 Thread-3 在尝试不成功后再次进入 park 状态
源码分析
获取信号量源码:
// 构造方法
public Semaphore(int permits) {
sync = new NonfairSync(permits); // 内部实际上是调用了同步器类,默认是非公平的同步器
}
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -2694183684443567898L;
NonfairSync(int permits) {
super(permits); // 内部调用的父类的构造,实际就是把传递的 premits 赋值给了 AQS 中的 state
}
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
}
// 获取许可
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1); // 获取许可数为 1
}
public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
if (Thread.interrupted()) {
throw new InterruptedException();
}
// tryAcquireShared() 尝试获取读锁,由子类实现,返回负数表示获取失败
// Semaphore 实现此方法代码在第 13 行
if (tryAcquireShared(arg) < 0) {
doAcquireSharedInterruptibly(arg);
}
}
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
// 拿到状态值
int available = getState();
// 剩余许可数
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining)) // 通过 CAS 无锁尝试修改
return remaining;
// 当 remaining < 0 后,不会执行后续的 CAS 操作,直接返回负数
}
}
// 如果许可已经没了,则后续线程会进入此方法
private void doAcquireSharedInterruptibly(int arg) throws InterruptedException {
// 创建 Node 节点,节点类型设置为共享读
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() :内部判断等待状态,再一次 CAS 重试尝试获取许可
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt()) // parkAndCheckInterrupt() 内部调用 park() 让线程阻塞等待
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == -1)
return true;
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 此时,等待状态为 SHARED,再次通过 CAS 操作重试
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
释放信号量源码:
public void release() {
// 释放一个许可
sync.releaseShared(1);
}
// 释放许可
public final boolean releaseShared(int arg) {
// tryReleaseShared() 尝试释放锁
if (tryReleaseShared(arg)) {
// 成功释放许可后,进入下方方法
doReleaseShared();
return true;
}
return false;
}
protected final boolean tryReleaseShared(int releases) {
for (;;) {
// 获得 state。如果没有许可了,则此处为 0
int current = getState();
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next)) { // 使用 CAS 的方式将 0 变成 1
return true;
}
}
}
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) { // 如果 waiteStatus 状态为 -1,表示有后继节点需要唤醒
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) { // 尝试先把 -1 改成 0
continue; // loop to recheck cases
}
// 唤醒后继节点 -> 跳转到加锁源码中的第 70 行
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
// 线程从 park 阻塞状态被唤醒后,进入此方法
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head;
setHead(node); // 设置当前节点为头节点
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared()) {
// 如果下一个节点是共享节点,唤醒后继节点
doReleaseShared();
}
}
}