互斥模式也可以称为独占模式,独占锁是互斥模式的实现(互斥模式的代码 在重入锁-Reetrantlock有讲解,这里不再详述)
//互斥模式获取锁的模板方法, tryAcquire 尝试通过CAS方式获取锁,由子类实现。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//互斥模式获取锁的模板方法,如果当前线程被打断,抛出打断异常。
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
//互斥模式获取锁的模板方法,如果指定纳秒内没有获得锁则中断获取,返回false。
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquire(arg) ||
doAcquireNanos(arg, nanosTimeout);
//互斥模式释放锁的模板方法,tryRelease(arg) 通过CAS释放锁,由子类实现
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
//共享模式获取锁的模板方法,tryAcquireShared 通过cas方式获取共享锁,由子类实现
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
与互斥模式相同 还有 acquireSharedInterruptibly,tryAcquireSharedNanos,releaseShared这几个模板,下面重点分析。
共享锁是共享模式的实现。
关键逻辑:唤醒一个节点后获取锁后,要唤醒下个节点来检查是否可以获得锁,如果可以不能获得锁,进入等待,如果能获取锁,则再唤醒下个节点检查
//doAcquireShared方法,阻塞和非阻塞过程中被打断,不抛出打断异常,只是记录打断状态(通过selfInterrupt())获取锁的流程与acquireQueued相似
private void doAcquireShared(int arg) {
//addWaiter 中 new Node(Thread.currentThread(), mode); 传入nextWaiter 属性 有什么用?
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head) {
//通过cas获取锁
int r = tryAcquireShared(arg);
if (r >= 0) {
//设置node为head,如果有必要则唤醒后续节点。
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
setHead(node);
// propagate > 0 有空位置
//h.waitStatus < 0 比较复杂后面重点分析
//h==null的情况 目前来看不可能
if (propagate > 0 || h == null || h.waitStatus < 0) {
Node s = node.next;
//s == null 证明当前节点是尾部节点,但是 有可能会立即加入新节点,s.isShared()指明是共享模式。这两种情况都需要释放后续节点(按照doAcquireShared中重点部分讲解到的 传递策略)。
if (s == null || s.isShared())
doReleaseShared();
}
}
释放共享锁方法
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
//传递策略的原因 有可能出现并发,利用CAS解决。
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
//PROPAGATE的用途?? 传递状态标识用于执行传递策略。
//如果不使用PROPAGATE可以么??,貌似没有办法 因为unparkSuccessor方法是通用的,解锁后ws=0,这点没办法改变,要标识需要执行传递策略必须定义一个标识值。
// 当第一个节点刚刚加入队列时,ws==0 这时如果没有获取锁 ws值 0->PROPAGATE>SIGNAL。其中PROPAGATE>SIGNAL在shouldParkAfterFailedAcquire执行。获得到了锁 唤醒下一个头节点。
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
只有 ws==0的情况下,waitStatus会从0改成PROPAGATE,有以下2种情况
新加入的节点waitStatus被doReleaseShared改为PROPAGATE
释放后的的节点doReleaseShared并发改成PROPAGATE
这2种情况有共同的特点,就是没有执行解除阻塞就获得了锁,推断出只有这种情况才需要传递。
setHeadAndPropagate中执行传递策略的条件是 h.waitStatus < 0,那么SIGNAL也符合,这正是让人迷糊的地方。假设h.waitStatus ==SIGNAL 并且h是head节点,说明这个节点刚入队后 doAcquireShared 自旋转的第一轮没有获得锁,随后waitStatus ==SIGNAL后第二轮自旋转获得了锁,说明 在将waitStatus 从0改为SIGNAL的期间或后面执行了doReleaseShared,其实也符合没有执行解除阻塞就获得了锁。
传递策略总结:执行了释放共享锁,然后没有执行解除阻塞就获得了锁才需要传递,PROPAGATE ,SIGNAL这2个状态值都符合这个要求。
获取共享锁打断方法,如果被唤醒后被打断抛出打断异常
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
//addWaiter 中 new Node(Thread.currentThread(), mode); 传入nextWaiter 属性 有什么用?
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);
}
}
获取共享锁timeout(单位纳秒)打断方法,如果被唤醒后被打断抛出打断异常
private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
总结:AQS定义了互斥模式和共享模式获取和释放锁的模板方法, 通过CAS获取和释放锁的方法由子类实现。