Semaphore通常用于在程序中做限流使用,控制一段程序同时只能有n个线程同时访问。
既然是同时有多个线程能同时访问,那Semaphore是用的肯定就是共享锁。其原理就是有线程获取了锁,就对state减,释放锁后就对state加。state小于0的时候,就对线程阻塞。一旦释放了锁,就唤醒等待线程。
Semaphore是通过共享锁实现的,如果对ReentrantReadWriteLock不熟悉的,请先看ReentrantReadWriteLock源码分析。弄明白了共享锁,理解Semaphore就很简单了。
Semaphore创建
//默认使用的是非公平锁
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
//传入的permits保存在state中
Sync(int permits) {
setState(permits);
}
semaphore.acquire
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//修改state的值,返回值为还剩余的state,如果小于0,说明state已经用完,则需要将线程挂起
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
//tryAcquireShared非公平锁的实现。
//非公平锁允许后来的线程直接获取锁。公平锁的实现,判断如果等待队列中有等待线程,就不允许后来的线程插队获取锁。
final int nonfairTryAcquireShared(int acquires) {
//每来一个线程,就将state的值做减法,返回值为还剩余的数量
//如果CAS失败了,说明有其他线程正在抢占,则继续下一次的循环再次尝试
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
//将线程封装为Node节点并挂起
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
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;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
semaphore.release
public final boolean releaseShared(int arg) {
//如果释放锁成功了,就唤醒线程
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
protected final boolean tryReleaseShared(int releases) {
//这里释放锁,和获取锁的逻辑是相反的,这里对state最加法,成功了就返回true,就会唤醒等地啊队列中的线程。
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;
}
}