文章目录
semaphore | BrE ˈsɛməfɔː, AmE ˈsɛməˌfɔr |
A.noun uncountable
(using arms) 臂板信号 bìbǎn xìnhào ; (using flags) 旗语 qíyǔ
B.transitive verb
(using arms) 用臂板信号发出 yòng bìbǎn xìnhào fāchū ; (using flags) 用旗语发出 yòng qíyǔ fāchū
信号量就是带有数量的共享锁。
用法
import java.util.concurrent.Semaphore;
public class Test {
public static void main(String[] args) {
Semaphore s = new Semaphore(1, true);
new Thread(()->{
try {
s.acquire();
System.out.println("T1 start...");
Thread.sleep(200);
System.out.println("T1 end...");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
s.release();
}
}).start();
new Thread(()->{
try {
s.acquire();
System.out.println("T2 start...");
Thread.sleep(200);
System.out.println("T2 end...");
s.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
输出:
T1 start...
T1 end...
T2 start...
T2 end...
源码
构造方法
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
Sync
Semaphore 内部类 Sync 继承了 AQS,同时有 FairSync 和 NonfairSync 2 个子类。这个结构和 [[ReentrantLock]] 很像。
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;
}
}
// 释放信号量
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
}
acquire()
该方法调用了 AQS 的 acquireSharedInterruptibly 方法。acquireSharedInterruptibly 再调用 Sync 的 tryAcquireShared 方法(这里分为公平和非公平两种实现)。
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
FairSync#tryAcquireShared
tryAcquireShared 的公平锁实现。
protected int tryAcquireShared(int acquires) {
for (;;) {
if (hasQueuedPredecessors()) // 若队列里已经线程在排队,那么直接返回-1
return -1;
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
NonfairSync#tryAcquireShared
直接调用 Sync 类的 nonfairTryAcquireShared 方法
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
nonfairTryAcquireShared
nonfairTryAcquireShared 返回值小于 0 表示没获取到信号量。
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 || // 没有可用资源
compareAndSetState(available, remaining)) // 更新可用资源数
return remaining;
}
}
release
release 调用 AQS 的 releaseShared。然后调用 Sync 的 tryReleaseShared
方法。
public void release() {
sync.releaseShared(1);
}
tryReleaseShared 释放信号量
tryReleaseShared
方法只要执行完成,那么就已经将信号量放入 state 变量中。
protected final boolean tryReleaseShared(int releases) {
for (;;) { // 允许多个线程同时释放读锁
int current = getState();
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
总结
伪唤醒问题
当结点状态为 -1 时,就算没有多余的信号量,也会唤醒后继结点。后继结点自行判断是否需要继续阻塞。
Node.PROPAGATE 问题
目前没有找到具体场景表明必须要设置结点 ws 为 Node.PROPAGATE 才能避免同时有多个线程释放信号量,导致信号量空闲,资源浪费。
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
重点关注这行代码:
else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue;
论据
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
setHead(node);
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) { // 满足 h.waitStatus < 0
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
}
若结点的状态为 -1,那么该结点满足 setHeadAndPropagate 中的条件判断,会唤醒下个结点。不管有多少空闲的信号量,都会链式唤醒后继结点。
若结点状态为 0,则表明后继结点还未 park(后继结点 park 之前必会将前置结点的 ws 改为 -1),等到当前结点是头结点时,后继结点就会尝试获取锁。
上面两种情况已经保证了只要有空闲的信号量,就会被线程获取,不存在资源浪费的情况。