部分启发来源自文章:Java并发编程--Lock
PART 1
1、如果h==t成立,h和t均为null或是同一个具体的节点,无后继节点,返回false。
2、如果h!=t成立,head.next是否为null,如果为null,返回true。什么情况下h!=t的同时h.next==null??,有其他线程第一次正在入队时,可能会出现。见AQS的enq方法,compareAndSetHead(node)完成,还没执行tail=head语句时,此时tail=null,head=newNode,head.next=null。
3、如果h!=t成立,head.next==null,则判断head.next是否是当前线程,如果是返回false,否则返回true(head节点是获取到锁的节点,但是任意时刻head节点可能占用着锁,也可能释放了锁(unlock()),未被阻塞的head.next节点对应的线程在任意时刻都是有必要去尝试获取锁)
1 public final boolean hasQueuedPredecessors() { 2 Node t = tail; 3 Node h = head; 4 Node s; 5 return h != t && 6 ((s = h.next) == null || s.thread != Thread.currentThread()); 7 }
PART 2 解释为什么要判断:s.thread != Thread.currentThread()
根据ReentrantLock的解锁流程,也就是下面四个方法,可以看到当线程释放锁之后还是会在队列的head节点,只是把next指针指向下一个可用节点,并唤醒它
也就是说任意时刻,head节点可能占用着锁(除了第一次执行enq()入队列时,head仅仅是个new Node(),没有实际对应任何线程,但是却“隐式”对应第一个获得锁但并未入队列的线程,和后续的head在含义上保持一致),也可能释放了锁(unlock()),未被阻塞的head.next节点对应的线程在任意时刻都是有必要去尝试获取锁
1 public void unlock() { 2 sync.release(1); 3 }
尝试释放锁,释放成功后把head.next从阻塞中唤醒
1 public final boolean release(int arg) { 2 if (tryRelease(arg)) { 3 Node h = head; 4 if (h != null && h.waitStatus != 0) 5 unparkSuccessor(h); 6 return true; 7 } 8 return false; 9 }
把state-1
当state=0时,把exclusiveOwnerThread设置为null,说明线程释放了锁
1 protected final boolean tryRelease(int releases) { 2 int c = getState() - releases; 3 if (Thread.currentThread() != getExclusiveOwnerThread()) 4 throw new IllegalMonitorStateException(); 5 boolean free = false; 6 if (c == 0) { 7 free = true; 8 setExclusiveOwnerThread(null); 9 } 10 setState(c); 11 return free; 12 }
把head.next指向下一个waitStatus<=0的节点,并把该节点从阻塞中唤醒
1 private void unparkSuccessor(Node node) { 2 int ws = node.waitStatus; 3 if (ws < 0) 4 compareAndSetWaitStatus(node, ws, 0); 5 6 Node s = node.next; 7 if (s == null || s.waitStatus > 0) { 8 // 这里没看懂为什么要从tail节点倒序遍历? 9 // 不是应该从head.next节点开始遍历更快嘛? 10 s = null; 11 for (Node t = tail; t != null && t != node; t = t.prev) 12 if (t.waitStatus <= 0) 13 s = t; 14 } 15 if (s != null) 16 LockSupport.unpark(s.thread); 17 }