Semaphore源码解析
简介
Semaphore是常用的同步工具之一,我们设置n,代表同一时间最多只有n个线程在访问
这个工具依赖AQS实现,所以在看源码前推荐先去看AQS的介绍,方便理解
public class SemaphoneTest {
private Semaphore semaphore = new Semaphore(5);
private ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 15, 60, TimeUnit.SECONDS, new LinkedBlockingDeque<>(20));
public void exec() {
try {
semaphore.acquire(1);
System.out.println("执行方法,时间:" + new Date());
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
} finally {
semaphore.release(1);
}
}
@Test
public void test() throws InterruptedException {
while ( true ) {
Thread.sleep(100);
executor.execute(() -> exec());
}
}
}
当我们的线程进来后,抢占成功的将会继续执行,抢占失败的就会进入同步队列,等待。
下面开始介绍他的源码
静态内部类
//继承了AQS,主要有两种实现,一种是公平的实现,一种是非公平的实现
abstract static class Sync extends AbstractQueuedSynchronizer {
Sync(int permits) {
setState(permits);
}
final int nonfairTryAcquireShared(int acquires) {
//自旋,然后如果剩余的state<0,直接返回remaining,否则尝试CAS修改state为剩余的
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)
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
}
然后是Sync的两个实现
NonFairSync
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -2694183684443567898L;
NonfairSync(int permits) {
super(permits);
}
//非公平的获取资源
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
}
FairSync
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;
}
}
}
然后是重要属性
重要属性
//所有的操作都是依靠这个AQS的实现类来进行的
//有公平和非公平两种,可选
private final Sync sync;
然后是常用的方法
主要有两个,一个是acquire(),一个是release()
acquire()
/* **
* 获取资源
*/
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
//如果线程被中断了,抛出异常
if (Thread.interrupted())
throw new InterruptedException();
//尝试去获取n个资源,小于0获取失败
//为0就不需要加入到阻塞队列
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
上面一段基本没做什么事情,关键在tryAcquireShared()以及doAcquireShareInterruptibly()两个中
如果尝试获取成功,就不需要进入到同步队列中
这边会有两种选择,一种是NonfairSync的实现,一种是FairSync的实现,先看NonFairSync的实现
NonfairSync.tryAcquireShared()
//非公平的获取资源
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
final int nonfairTryAcquireShared(int acquires) {
//自旋,然后如果剩余的state<0,直接返回remaining,否则尝试CAS修改state为剩余的
//这里就是看我们的state在减去我们需要的数据之后,还剩下下多少,会配合前面的<0来看是不是需要入队
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
上面是我们的NonFairSync的流程,下面来看我们的FairSync的流程
FairSync.tryAcquireShared()
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;
}
}
//其实也就是看头节点后面是不是有元素,并且这个元素不是自己
public final boolean hasQueuedPredecessors() {
AbstractQueuedSynchronizer.Node t = tail; // Read fields in reverse initialization order
AbstractQueuedSynchronizer.Node h = head;
AbstractQueuedSynchronizer.Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
其实和非公平的差不多,不过要多了一个判断条件,也就是是不是有已经在同步队列中排队的线程,
因为是公平锁,所以不能插队
然后如果没有成功的获取到我们的state,我们就会尝试入队,
doAcquireSharedInterruptibly()
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);
//如果getState为0,说明count为0,等待的线程可以执行了
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);
}
}
如果没有获取成功就会把当前线程包装成一个Node,然后加入到队列中
然后来看我们的释放逻辑
release()
public void release() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
//尝试修改我们的state
if (tryReleaseShared(arg)) {
//修改成功,开始释放
doReleaseShared();
//state==0,所以从队列里面一个接一个的退出
return true;
}
return false;
}
tryReleaseShared()
protected final boolean tryReleaseShared(int releases) {
//自旋,然后将我们的state加上一定的值,然后CAS尝试修改
for (;;) {
int current = getState();
int next = current + releases;
if (next < current)
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
doReleaseShared()
//尝试自旋然后去唤醒其他的节点,然后其他的节点就回去尝试进行调用tryAcquireShared去进行获取
private void doReleaseShared() {
for (;;) {
AbstractQueuedSynchronizer.Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == AbstractQueuedSynchronizer.Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, AbstractQueuedSynchronizer.Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, AbstractQueuedSynchronizer.Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
总结
其实,这个的获取资源大概逻辑是这样的
非公平模式
- 查看state减去自己需要的数量
- 如果剩余数量小于0,准备开始进入队列尝试自旋
- 如果剩余数量大于0并且CAS修改成功,直接返回,代表获取成功
- 进入队列是将自己包装成一个共享模式的Node节点,然后自旋,如果自己的前驱不是头节点,就会被阻塞
- 被唤醒后,还在自旋的循环中,然后判断如果前驱是头节点,就会去尝试修改我们的state,修改成功,就会去设置头节点,失败的话就会继续进行自旋
公平模式
- 查看同步队列中是不是有线程,有的话直接进入队列阻塞,没有的话进行底下的步骤
- 查看state减去自己需要的数量
- 如果剩余数量小于0,准备开始进入队列尝试自旋
- 如果剩余数量大于0并且CAS修改成功,直接返回,代表获取成功
- 进入队列是将自己包装成一个共享模式的Node节点,然后自旋,如果自己的前驱不是头节点,就会被阻塞
- 被唤醒后,还在自旋的循环中,然后判断如果前驱是头节点,就会去尝试修改我们的state,修改成功,就会去设置头节点,失败的话就会继续进行自旋
然后是释放的逻辑,两边都是一样的
- 尝试去自旋的CAS修改我们的state为state+释放的量
- 修改成功后,自旋的去唤醒节点,然后这些被唤醒的节点又会去尝试获取资源