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 }
  • 相关阅读:
    ZMQ面面观
    windows10系统右键新建菜单的自定义
    元组,列表,字典前加*
    HTTP状态码(转)
    字符串利用%02d将月份前加0
    python中while与else的联姻
    sys.argv
    pandas的read_csv踩到的坑
    wireshark抓包总结
    bcolz
  • 原文地址:https://www.cnblogs.com/kumu/p/10659835.html
Copyright © 2011-2022 走看看