从Semaphore的acquire()方法开始
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
可见acquire方法调用了Sync的acquireSharedInterruptibly(1)方法,此方法在类AbstractQueuedSynchronizer中,而Sync类继承了AbstractQueuedSynchronizer类,下面我们看看此方法的实现
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
acquireSharedInterruptibly(int arg)方法首先会判断当前线程是否被中断过,如果被中断过就抛InterruptedException异常,所以Semaphore类是响应Thread的interrupt方法的。如果线程没有被中断的话就回去调用子类的tryAcquireShared方法,此方法时在Sync类的两个子类都有实现,我们看看公平锁的实现
static final class FairSync extends Sync {
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;
}
}
}
由于公平锁,所以会调用hasQueuedPredecessors方法判断是否有线程在同步队列中等待,这部分内容已经在独占锁下的AQS讲过了,就不赘述了。
如果没有线程在等待,就获取到AbstractQueuedSynchronizer中的state属性,需要注意的是我们在初始化Semaphore类时,需要传入一个permits参数
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
static final class NonfairSync extends Sync {
NonfairSync(int permits) {
super(permits);
}
}
abstract static class Sync extends AbstractQueuedSynchronizer {
// 最终会在这里设置state属性
Sync(int permits) {
setState(permits);
}
}
这个参数就是锁可以被多少个线程占用的意思,当获取到state之后,和需要的信号(在Semaphore类中是1)相减剩下的state是否小于0,如果小于0,证明state已经被抢完了,不能再抢了!如果state大于等于0,那么就是用cas设置state,如果失败就自选重试,如果成功就返回。所以返回值决定了抢占锁失败还是成功,小于0就是失败,大于0就是成功!
回到AbstractQueuedSynchronizer中
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
如果tryAcquireShared(arg)方法返回值小于0,那么就会进行阻塞操作,调用doAcquireSharedInterruptibly(arg)方法
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);
}
}
如果看过我这篇独占锁下的AQS就会发现共享锁和独占锁在使线程阻塞时逻辑相差不大
首先会将线程加入到同步队列中,模式是Node.SHARED共享模式,接下来自旋,判断前一个节点是否是头结点,我们知道aqs的队列的头结点是一个空节点,如果前面的节点是头结点的话,证明此时当前线程在同步队列中排第一,是有可能抢到锁的,再次调用tryAcquireShared(arg)方法,如果抢锁成功,此方法会返回大于等于0,那么设置头结点,然后返回;
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) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
}
如果返回值propagate大于0的话还回去进行锁的释放,调用doReleaseShared()释放锁。
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;
}
}
此方法不仅在这里会调用,在Semaphore类释放共享锁时也会被调用。
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
下面来具体分析doReleaseShared()方法。
如果h == null,证明队列中没有节点,不需要唤醒,如果h==null && h == tail,证明当前同步队列中只有一个节点,头结点是一个空节点,也不需要唤醒!
h != null && h != tail这个条件保证了不同队列中一定有两个节点。如果头结点的waitStatus属性为SIGNAL,那么它需要去唤醒后继节点,此时需要使用cas设置waitStatus属性为0,这是因为doReleaseShared()方法可以被多个线程同时执行,去唤醒后继节点只需要一个线程就够了。
else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))这个条件有两种情况会进入
- 当多个线程同时进入到
h != null && h != tail之后,此时waitStatus属性为SIGNAL,那么就会有一个线程成功设置waitStatus属性为0,一些线程重新自旋,一些线程进入了else if,使用cas设置waitStatus属性为PROPAGATE - 当队列中只有两个节点并且刚进来的线程在刚加入队列,但是并没有来的及设置前一个节点的
waitStatus属性为SIGNAL,此时也会进入else if,使用cas设置waitStatus属性为PROPAGATE
至于为什么要把waitStatus属性为PROPAGATE(-3),是因为有这么一种情况!
import java.util.concurrent.Semaphore;
public class TestSemaphore {
private static Semaphore sem = new Semaphore(0);
private static class Thread1 extends Thread {
@Override public void run() {
sem.acquireUninterruptibly();
}
}
private static class Thread2 extends Thread {
@Override public void run() {
sem.release();
}
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10000000; i++)
{
Thread t1 = new Thread1();
Thread t2 = new Thread1();
Thread t3 = new Thread2();
Thread t4 = new Thread2();
t1.start();
t2.start();
t3.start();
t4.start();
t1.join();
t2.join();
t3.join();
t4.join();
System.out.println(i);
}
}
}
- 在t1时刻,线程1和线程2都去获取锁,但是由于
Semaphore类的初始值为0,导致state减一小于0,两个线程都会被阻塞,此时在同步队列的节点如图
- t2时刻,线程3去调用release,此时会将state+1变为1,然后head节点进入
unparkSuccessor方法,设置自己的waitStatus为0,唤醒了线程1,此时线程1会去抢占锁 - t3时刻,线程4去调用
doReleaseShared,由于此时线程1还在抢占锁,没有来的及替换head节点,所以t4发现head节点的waitStatus为0,因此不会调用unparkSuccessor方法,进入到else if中,假设线程t4不去设置head节点的waitStatus为PROPAGATE - t4时刻,线程1抢占到了锁,会调用
setHeadAndPropagate方法,由于线程1抢到锁之后state又变为了0,那么propagate > 0这个条件就不成立,而后面的判断条件不成立,线程t1就不会去调用doReleaseShared方法,线程t2就永远在同步队列中
这就是为什么需要设置head节点的waitStatus状态为PROPAGATE的原因
最后,我们分析下h == head这个条件,A线程在调用unparkSuccessor方法之后,会解锁B线程,使得B线程获取到锁,此时B线程就成为了head节点,如果B线程在获取到锁之后调用setHeadAndPropagate方法时发现if的条件满足,那么B线程也会进入到doReleaseShared方法中,此时A线程和B线程就一起对来唤醒同步队列中的线程(如果A线程还没有运行完毕),有位博主称这个过程叫‘调用风暴’,加速了同步队列中的线程唤醒的过程!就算A线程运行完毕了,B线程也会继续唤醒同步队列的线程。
参考:下面的两篇博客都很优秀,尤其是第二篇