zoukankan      html  css  js  c++  java
  • ReentrantLock类的hasQueuedPredecessors方法和head节点的含义

    部分启发来源自文章: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()

    评论区3楼的提问差点让我以为我这里理解错并写错了,现在是12月,文章是4月份写的,都快忘光了...仔细再把文章和源码读了读,发现本文写的确实不够详细,有个地方还写的有点问题,漏了一些细节,因此来补充一下。  ---20191217

    1、

    根据ReentrantLock的解锁流程,也就是下面四个方法,可以看到当线程释放锁之后还是会在队列的head节点,但会把head的后续可唤醒节点进行唤醒(unpark)
    也就是说任意时刻,head节点可能占用着锁(除了第一次执行enq()入队列时,head仅仅是个new Node(),没有实际对应任何线程,但是却“隐式”对应第一个获得锁但并未入队列的线程,和后续的head在含义上保持一致),也可能释放了锁(unlock()),未被阻塞的head.next节点对应的线程在任意时刻都是有必要去尝试获取锁

    1 public void unlock() {
    2     sync.release(1);
    3 }

    2、

    尝试释放锁,释放成功后把head.next从阻塞中唤醒

    从这里以及后续的3和4可以看出,虽然线程已经释放了锁(state设置为0),但是并没有把head指向链表的下个节点(即进行类似head = head.next的操作)

    这里就对应的第1点里说的,如果这里能看到,那么久可以直接看第5点了

    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 }

    3、

    把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 }

    4、

    把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 }

    5、

    需要提前知道一点:hasQueuedPredecessors()方法只在tryAcquire()方法里面被调用执行过,hasQueuedPredecessors()返回false表示要尝试获取锁

    线程加锁的流程是:.lock() -> .acquire() -> tryAcquire()

    这里我们先假设一个场景:A线程获取到了锁,然后B线程尝试去获取锁但是获取不到,此时链表的head是对用A线程,head.next对应B线程

    当在B线程在第2行的tryAcquire()里面无法获取到锁时,线程B会通过下面第3行的addWaiter()方法被加入到等待链表当中,然后在第3行的acquireQueued()方法和第38行的parkAndCheckInterrupt()中park进入等待状态

    在A线程释放锁之后,B线程会从38行处开始重新苏醒然后进入for(;;)循环,当B线程执行到第28行即再次执行tryAcquire()时,然后就会依次执行hasQueuedPredecessors()和s.thread != Thread.currentThread()。由前文可知,此时head仍然指向A线程,head.next也就是此处的s指向的是B线程,也同时是当前线程,所以s.thread != Thread.currentThread()为false,即此时需要尝试获取锁(再次重复这句话:未被阻塞的head.next节点对应的线程在任意时刻都是有必要去尝试获取锁)。

    当此处B线程终于获得锁之后,会在第30行处把head指向B线程对应的链表结点。

     1 public final void acquire(int arg) {
     2     if (!tryAcquire(arg) &&
     3         acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
     4         selfInterrupt();
     5 }
     6 
     7 protected final boolean tryAcquire(int acquires) {
     8     // 省略部分不重要的
     9     
    10     if (c == 0) {
    11         if (!hasQueuedPredecessors() &&
    12             compareAndSetState(0, acquires)) {
    13             setExclusiveOwnerThread(current);
    14             return true;
    15         }
    16     }
    17     
    18     // 省略部分不重要的
    19 }
    20 
    21 final boolean acquireQueued(final Node node, int arg) {
    22     boolean failed = true;
    23     try {
    24         boolean interrupted = false;
    25         for (;;) {
    26             final Node p = node.predecessor();
    27             // 这里又执行了tryAcquire
    28             if (p == head && tryAcquire(arg)) {
    29                 // 把head指向当前节点
    30                 setHead(node);
    31                 p.next = null; // help GC
    32                 failed = false;
    33                 return interrupted;
    34             }
    35             // 获取不到锁,会在此处进入线程等待状态
    36             // 后续被唤醒的话,也是从这里出来,然后继续for循环
    37             if (shouldParkAfterFailedAcquire(p, node) &&
    38                 parkAndCheckInterrupt())
    39                 interrupted = true;
    40         }
    41     } finally {
    42         if (failed)
    43             cancelAcquire(node);
    44     }
    45 }
  • 相关阅读:
    2016"百度之星"
    codeforces 55 div2 C.Title 模拟
    codeforces 98 div2 C.History 水题
    codeforces 97 div2 C.Replacement 水题
    codeforces 200 div2 C. Rational Resistance 思路题
    bzoj 2226 LCMSum 欧拉函数
    hdu 1163 九余数定理
    51nod 1225 余数的和 数学
    bzoj 2818 gcd 线性欧拉函数
    Codeforces Round #332 (Div. 2)D. Spongebob and Squares 数学
  • 原文地址:https://www.cnblogs.com/kumu/p/10659835.html
Copyright © 2011-2022 走看看