zoukankan      html  css  js  c++  java
  • AQS系列(二)- ReentrantLock的释放锁

    前言

        在AQS系列(一)中我们一起看了ReentrantLock加锁的过程,今天我们看释放锁,看看老Lea那冷峻的思维是如何在代码中笔走龙蛇的。

    正文

        追踪unlock方法:

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

        很简单的一行,调用了release方法,参数为1,继续跟踪发现不管是公平锁还是非公平锁调用的都是AbstractQueuedSynchronizer中的release方法:

    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     }

    此方法看起来简单,却暗含杀机。

    1、首先看if中的判断方法tryRelease

     1 protected final boolean tryRelease(int releases) {
     2             int c = getState() - releases; // 计算出释放锁之后的state值
     3             if (Thread.currentThread() != getExclusiveOwnerThread())
     4                 throw new IllegalMonitorStateException();
     5             boolean free = false;
     6             if (c == 0) { // c==0说明要释放锁了
     7                 free = true;
     8                 setExclusiveOwnerThread(null); //在释放之前将独占线程置为空
     9             } 
    10 setState(c); // 将state置为0,此处没用cas操作,因为没必要,反正在此之前state都大于0,不会被其他线程操作,只有当前线程能操作
    11 return free;
    12 }

    此方法的实现逻辑在ReentrantLock类的Sync内部类中,即公平锁和非公平锁公用,相信理解起来比较轻松。

    2、再看里面的if判断条件 h != null && h.waitStatus != 0

        注意此时h是head,队列头。我们先要搞清楚这两个判断条件所表示的意思,h!=null说明队列不是空的,而h.waitStatus != 0又是什么意思呢?回顾一下上一篇的最后第二个方法 shouldParkAfterFailedAcquire,当时讲这个方法时其实描述的不是很清楚,这次重新结合释放锁的场景回顾一下。下面先将该方法粘贴出来(注释中的两个2表示执行一次这个方法只会走一个2的逻辑):

     1 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
     2         int ws = pred.waitStatus; // 1、正常情况进到这里ws是0,pred可能是head,也可能只是node前面另一个排队的任务
     3         if (ws == Node.SIGNAL)
     4             // 3、如果是-1了,就返回true,进入后面park当前线程
     5             return true;
     6         if (ws > 0) {
     7             do {
     8                 // 2、如果是大于0,说明pred线程已经被取消,则继续往前遍历,直到从后往前找到第一个不大于0的节点,然后互相设置指针
     9                 node.prev = pred = pred.prev;
    10             } while (pred.waitStatus > 0);
    11             pred.next = node;
    12         } else {
    13             // 2、是0的话进这里,设置成-1,注意是将pred(即当前node的前一个节点)设置成-1。即如果一个节点ws是-1,那么它后面一定至少还有一个node(就是这个方法中的node)
    14             compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    15         }
    16         return false;
    17     }

         waitStatus>0只有一种情况-线程被取消了(状态值为1)。当线程被取消时就要舍弃掉它,继续往前遍历。

        回顾完上述的方法,再看h.waitStatus != 0,我们可以知道,waitStatus != 0表示等待后面还有排队的node(可能是正常状态也可能是已取消的状态),这时就要去唤醒下一个正常状态的线程,进入unparkSuccessor方法。

     3、unparkSuccessor 方法代码

     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             s = null;
     9             for (Node t = tail; t != null && t != node; t = t.prev)
    10                 if (t.waitStatus <= 0)
    11                     s = t;
    12         }
    13         if (s != null)
    14             LockSupport.unpark(s.thread);
    15     }

        该方法用于唤醒当前线程的下一个有效任务,入参node为head节点。首先如果ws为-1则通过CAS设置为0;然后判断node的下一个节点是不是空,或者是不是已经被取消(ws大于0表示已经被取消);如果满足条件,则从后往前遍历找到从前往后数的第一个ws小于等于0的node节点,唤醒这个节点的线程。此处的for循环用的比较有意思,用了一种类似于while循环的格式来用for循环,可见老Lea不拘一格的思维方式。

        此处最后一行unpark方法执行之后,就会进入系列(一)中的最后一个方法的第3行代码(如下所示),继续执行下一个线程的加锁过程,进入下一次轮回。

    1 private final boolean parkAndCheckInterrupt() {
    2         LockSupport.park(this);
    3         return Thread.interrupted();
    4     }

    附加:公平锁与非公平锁的源码理解

        在上一篇文章中未讲到公平锁和非公平锁的区别,在这里统一进行一下总结:

        在释放锁的过程中,公平锁和非公平锁的处理流程是一样的,都是从队列的头往后遍历挨个唤醒等待的线程。

        在加锁的过程中,有两个不同的地方。第一个是在lock方法中,公平锁代码:

    1 final void lock() {
    2             acquire(1);
    3         }

        非公平锁代码:

    1 final void lock() {
    2             if (compareAndSetState(0, 1))
    3                 setExclusiveOwnerThread(Thread.currentThread());
    4             else
    5                 acquire(1);
    6         }

        可以看到非公平锁直接先用CAS尝试获取一下锁,不用排队。这就是第一个非公平的地方。

        第二个不同的地方,是acquire方法中的tryAcquire方法实现不同,公平锁的tryAcquire方法:

     1 protected final boolean tryAcquire(int acquires) {
     2             final Thread current = Thread.currentThread();
     3             int c = getState();
     4             if (c == 0) {
     5                 if (!hasQueuedPredecessors() &&
     6                     compareAndSetState(0, acquires)) {
     7                     setExclusiveOwnerThread(current);
     8                     return true;
     9                 }
    10             }
    11             else if (current == getExclusiveOwnerThread()) {
    12                 int nextc = c + acquires;
    13                 if (nextc < 0)
    14                     throw new Error("Maximum lock count exceeded");
    15                 setState(nextc);
    16                 return true;
    17             }
    18             return false;
    19         }

        可以看到当c==0时公平锁会先通过hasQueuedPredecessors方法判断队列前面有没有排队的。

        非公平锁的实现:

     1 protected final boolean tryAcquire(int acquires) {
     2             return nonfairTryAcquire(acquires);
     3         }
     4 
     5 
     6 final boolean nonfairTryAcquire(int acquires) {
     7             final Thread current = Thread.currentThread();
     8             int c = getState();
     9             if (c == 0) {
    10                 if (compareAndSetState(0, acquires)) {
    11                     setExclusiveOwnerThread(current);
    12                     return true;
    13                 }
    14             }
    15             else if (current == getExclusiveOwnerThread()) {
    16                 int nextc = c + acquires;
    17                 if (nextc < 0) // overflow
    18                     throw new Error("Maximum lock count exceeded");
    19                 setState(nextc);
    20                 return true;
    21             }
    22             return false;
    23         }

        当c==0时,非公平锁是直接用CAS尝试获取加锁。这是第二个非公平的地方。

        好了,ReentrantLock的加锁和释放锁过程基本就这些了,这周末继续搞JUC!

  • 相关阅读:
    汉语-词语:冷静
    汉语-词语:沉着
    汉语-词语-稳重:百科
    汉语-词语:沉稳
    汉语-词语-丘壑:百科
    Struts中的常量
    算法整理(四):浅析高速排序的优化问题
    互联网+时代,是更加开放还是封闭
    UI复习练习_优酷布局
    fread与read的差别(文件io补充)
  • 原文地址:https://www.cnblogs.com/zzq6032010/p/12026470.html
Copyright © 2011-2022 走看看