背景:一个线程加了两次锁,可重入释放锁的过程
public void unlock() {sync.release(1);}public final boolean release(int arg) {//1.尝试释放锁if (tryRelease(arg)) {//2.唤醒线程流程Node h = head;if (h != null && h.waitStatus != 0)unparkSuccessor(h);return true;}return false;}
1.tryRelease(arg),尝试释放锁
//ReentrantLockprotected final boolean tryRelease(int releases) {//1int c = getState() - releases;//2if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;//3if (c == 0) {free = true;setExclusiveOwnerThread(null);}//4setState(c);return free;}
第一释放锁
- int c = getState() - releases; 这里getState()为2,因为第一个线程加了锁,releases为1
- Thread.currentThread() != getExclusiveOwnerThread();当前线程不是加锁线程,说明不是当前线程加锁反而来释放锁,如果能释放就是不对的
- 由于现在state减了1还是1,那么这个判断进不去,如果能进去就代表释放锁成功了
- 更改state的值
第二次释放锁
- int c = getState() - releases; 这里getState()为1,因为第一个线程加了锁,releases为1
- 该判断不进入
-
2.唤醒线程流程
public final boolean release(int arg) {if (tryRelease(arg)) {//唤醒线程流程Node h = head;if (h != null && h.waitStatus != 0)unparkSuccessor(h);return true;}return false;}
Node h = head; 搞一个中间变量h 指向head节点

unparkSuccessor(h);
- int ws = node.waitStatus;,head节点的status,此时是个signal
- compareAndSetWaitStatus(node, ws, 0); 就可以执行这个操作,把head节点的status设置成0
- Node s = node.next;拿到第一个等待线程节点,也就是线程2节点
由于s不是null,就能执行LockSupport.unpark(s.thread);,这个方法就传入了s节点中的Thread进行唤醒
private void unparkSuccessor(Node node) {/** If status is negative (i.e., possibly needing signal) try* to clear in anticipation of signalling. It is OK if this* fails or if status is changed by waiting thread.*/int ws = node.waitStatus;if (ws < 0)compareAndSetWaitStatus(node, ws, 0);/** Thread to unpark is held in successor, which is normally* just the next node. But if cancelled or apparently null,* traverse backwards from tail to find the actual* non-cancelled successor.*/Node s = node.next;if (s == null || s.waitStatus > 0) {s = null;for (Node t = tail; t != null && t != node; t = t.prev)if (t.waitStatus <= 0)s = t;}if (s != null)LockSupport.unpark(s.thread);}
此时,切回到线程2阻塞的代码中
- 唤醒后,线程2就在这个for循环中,此时获取p一定时head,因此就开始执行tryAcquire开始获取锁
为什么从尾部开始唤醒
既然采用了从尾部遍历的逻辑,那么肯定是为了解决可能会出现的问题。而这个问题就在enq(…)方法中:
private Node enq(final Node node) {for (;;) {Node t = tail;if (t == null) { // Must initialize//队列为空需要初始化,创建空的头节点if (compareAndSetHead(new Node()))tail = head;} else {node.prev = t;//set尾部节点if (compareAndSetTail(t, node)) {//当前节点置为尾部t.next = node; //前驱节点的next指针指向当前节点return t;}}}}
在该段方法中,将当前节点置于尾部使用了CAS来保证线程安全,但是请注意:在if语句块中的代码并没有使用任何手段来保证线程安全!
也就是说,在高并发情况下,可能会出现这种情况:
线程A通过CAS进入if语句块之后,发生上下文切换,此时线程B同样执行了该方法,并且执行完毕。然后线程C调用了unparkSuccessor方法。
- 线程A执行CAS将当前节点置为尾部:

- 原本线程A要执行t.next = node;将node2的next设置为node3,但是,此时发生上下文切换,时间片交由线程B,也就是说,此时node2的next还是null
- 线程B执行enq逻辑,最终CLH队列如图所示:

- 此时发生上下文切换,时间片交由线程C,线程C调用了unparkSuccessor方法,假如是从头到尾的遍历形式,在node2就会发现,next指针为null,似乎没有后续节点了。
其最根本的原因在于:
node.prev = t;先于CAS执行,也就是说,你在将当前节点置为尾部之前就已经把前驱节点赋值了,自然不会出现prev=null的情况
