并发编程之JUC(二):ReentrantLock的源码分析二

一. 锁的释放

上一节中,我们讲完了加锁的过程,本节我们先来讲锁是如何释放的,还是以我们上节的那个最简单的例子,使用unlock解锁,这里有一个常识,unlock需要在finally语句块中,不然抛异常了,就会导致死锁。

public class Test {

    private static ReentrantLock reentrantLock = new ReentrantLock();
    public static void main(String[] args) {
        try {
            reentrantLock.lock();
            System.out.println("XXXXXXXXXXX");
        }finally {
            reentrantLock.unlock();
        }
    }
}

点开unlock方法,还是那个sync,调用了父类AQS的release方法,同加锁差不多,首先调用tryRelease方法去尝试释放锁,由Sync类实现

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

首先获取到state的值,state的值 - 1代表了锁被多少次重入了,因此此时使用state - releases表示,当前线程释放后还需不需要再次unlock,直到state被减至0后,才能表示所有重入的锁都被释放了,这是其他线程才能加锁成功。
首先要判断当前锁跟持有锁的线程是否是同一线程,如果不是则直接抛出异常,因此我们不能通过另起一个线程的方式来释放掉锁,只能按照排队的方式由当前线程来释放。
当state减成0的时候,表示当前锁可以被释放掉了,此时设置free=true,持有锁的线程置为null

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}
  • 如果放回false,表示当前锁有重入,需要再次调用unlock来释放,那我们直接unlock,不lock会发生什么呢?不lock一下exclusiveOwnerThread就会为null,Thread.currentThread() != getExclusiveOwnerThread()条件通过,抛出异常。因此lock N次就必须要unlock N次,lock个数大于unlock个数导致死锁,unlock大于lock抛异常。
  • 如果tryRelease返回true,判断头节点的状态是否不为0
Node h = head;
if (h != null && h.waitStatus != 0)
    unparkSuccessor(h);
return true;

这里只判断头节点是因为只有头节点的下一个节点才能执行到unlock操作,因此只需要判断head节点的waitStatus是否为-1,即Node.SIGNAL,由于在加锁的时候我们已经把所有等待节点的上一个节点的waitStatus的状态变为了-1,因此这里ws<0肯定是满足的,故而把head节点的waitStatus置为0

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);
}

此时各节点的状态将会是如下这样
在这里插入图片描述
这是拿到head的下一个节点node2,条件不满足,这里要满足的话涉及到锁的取消,而锁的取消就涉及到我们上一节中没有讲到的一个方法,cancelAcquire,这个方法只有在我们使用lockInterruptibly()加锁时才会触犯,这个我们后面再讲,而这里就是从尾部去递归查找离head最近的一个没有被取消的锁进行释放

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

这里我们肯定找到的是头节点的下一个节点,对它进行释放,释放完成后,还记得我们上一节中讲的那个阻塞吗?parkAndCheckInterrupt()中调用的LockSupport.park(this);这里就会被解开,继续进行下一次for循环,去获取锁,在没有了其他线程竞争的前提下,这里就一定会获取到锁,当前节点编程head节点,原head节点失去引用,随后被GC回收。

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

如果假设此时只有这一次节点,没有node3,那么tail和head节点都将指向这一个。
在这里插入图片描述

二. 锁的取消

OK,释放锁至此就走完了,下面我们来看一下刚才提到的cancelAcquire方法,锁的取消并没有提供开放的API来给程序员调用,而是当我们调用lockInterruptibly方法时,这个方法最终逻辑由AQS实现,区别在于该方法实现的锁是可以被打断的,以下是main方法的调用。

public class Test {

    private static ReentrantLock reentrantLock = new ReentrantLock();
    public static void main(String[] args) {
        try {
//            reentrantLock.lock();
            reentrantLock.lockInterruptibly();
            System.out.println("XXXXXXXXXXX");
        }catch(InterruptedException e){
            
        }finally {
            reentrantLock.unlock();
        }
    }
}

基本思路与lock中的acquireQueued方法差不多,唯一的区别在于这个方法会在parkAndCheckInterrupt方法检测到当前线程被其他线程打断之后,会直接抛出异常,然后会进入finally的逻辑,执行cancelAcquire方法

private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

下面对cancelAcquire方法详细的分析以下

private void cancelAcquire(Node node) {
  // Ignore if node doesn't exist
  if (node == null)
      return;

  node.thread = null;

  // Skip cancelled predecessors
  Node pred = node.prev;
  while (pred.waitStatus > 0)
      node.prev = pred = pred.prev;

  // predNext is the apparent node to unsplice. CASes below will
  // fail if not, in which case, we lost race vs another cancel
  // or signal, so no further action is necessary.
  Node predNext = pred.next;

  // Can use unconditional write instead of CAS here.
  // After this atomic step, other Nodes can skip past us.
  // Before, we are free of interference from other threads.
  node.waitStatus = Node.CANCELLED;

  // If we are the tail, remove ourselves.
  if (node == tail && compareAndSetTail(node, pred)) {
      compareAndSetNext(pred, predNext, null);
  } else {
      // If successor needs signal, try to set pred's next-link
      // so it will get one. Otherwise wake it up to propagate.
      int ws;
      if (pred != head &&
          ((ws = pred.waitStatus) == Node.SIGNAL ||
           (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
          pred.thread != null) {
          Node next = node.next;
          if (next != null && next.waitStatus <= 0)
              compareAndSetNext(pred, predNext, next);
      } else {
          unparkSuccessor(node);
      }

      node.next = node; // help GC
  }
}
  • 假设我们已经有4个线程在这里等待锁
    在这里插入图片描述
  • 此时node3线程被一个线程打断了,进入到了cancelAcquire方法,首先把持有的线程thread置为null
  • 然后判断当前线程的上一个node节点的waitStatus是否大于0,如果大于零,则表示上一个也是被取消了的,整个链就会跳过上一个
  • Node predNext = pred.next此处拿到当前node本身
  • 把当前node的waiStatus置为1
    在这里插入图片描述
  • 判断node3是否已经是最后一个node,如果是就把上面循环获取到的第一个非取消状态的node2赋值给tail,并且把node2的next置为null,简单来说就是把尾部的自己从链路删除,此处node3的情况不满足,走else逻辑
    在这里插入图片描述
  • else判断中如果node2不是head节点,并且它的waitStatus=-1,并且node2持有的线程不为null,满足,然后把node2的不为空且在等待锁的next指向node4,next已经跳过了node3
    在这里插入图片描述
    此时node3被打断后,就会走到走到finally中的unlock方法,而如果判断当前线程与持有锁的线程时就会抛出IllegalMonitorStateException异常
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

而如果node2也被打断了的时候,调用cancelAcquire方法,这时会通过将node2的waitStatus置为0,thread置为null,然后走到else的unparkSuccessor方法,将node2的next节点unpark

else {
    unparkSuccessor(node);
}

根据上面的图可以看到node2的next现在是node4.
在这里插入图片描述
node4被唤醒后,会继续进入doAcquireInterruptibly方法的死循环,判断p的上一个节点是否是头节点,很明显,node4的上一个节点还是node3.

for (;;) {
    final Node p = node.predecessor();
    if (p == head && tryAcquire(arg)) {
        setHead(node);
        p.next = null; // help GC
        failed = false;
        return;
    }
    if (shouldParkAfterFailedAcquire(p, node) &&
        parkAndCheckInterrupt())
        throw new InterruptedException();
}

因此进入shouldParkAfterFailedAcquire(p, node)判断,传入pred【node3】,node【node4】,
判断到node3的waiStatus>0了,因此递归循环递归,发现node3和node2的waitStatus都大于0,直到head节点node1,此时仍在阻塞,waitStatus=-1,因此将node4的prev指向node1,node1的next指向node4,于是完成节点跳过了取消的节点.

这个循环比较难理解,首先是pred=pred.prev,这个意思就是把node2直接赋值给node3,因此原来的node3的变量直接指向了node2,再执行node.prev=pred,意思是把node4的prev指向node2,因此node3就永远失去了引用,再循环遍历一次,node4的prev就指向了node1,最后再把node1的next指向node4,node2也失去了引用,最后被GC回收。最终形成的结构如下图:
在这里插入图片描述

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        /*
         * This node has already set status asking a release
         * to signal it, so it can safely park.
         */
        return true;
    if (ws > 0) {
        /*
         * Predecessor was cancelled. Skip over predecessors and
         * indicate retry.
         */
         // 这一段理解起来
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        /*
         * waitStatus must be 0 or PROPAGATE.  Indicate that we
         * need a signal, but don't park yet.  Caller will need to
         * retry to make sure it cannot acquire before parking.
         */
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

跳过后再次进入doAcquireInterruptibly循环,尝试获取锁,如果此时node1的线程还没有执行完成,node4将再次park起来等待。

以上就是cancelAcquire的基本流程,很多地方因为是并发所以不好测试,我这里有一份测试方法,可以用来参考一下。

public class Test {

    private static ReentrantLock reentrantLock = new ReentrantLock(true);
    public static Map<Integer, Thread> map = new ConcurrentHashMap<Integer, Thread>();
    public static void main(String[] args) {

        for(int i = 0; i < 20; i++){
            final int fi = i;
            new Thread(new Runnable() {
                public void run() {
                    try {
                        Thread t = Thread.currentThread();
                        Thread.sleep(fi * 1000);
                        System.out.println(fi + ":" + t.getName());
                        map.put(fi, t);
                        reentrantLock.lockInterruptibly();
                        Thread.sleep(500000000);
                        System.out.println("XXXXXXXXXXX");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        reentrantLock.unlock();
                    }
                }
            }).start();
        }

        Scanner scanner = new Scanner(System.in);
        while(scanner.hasNext()){
            String commond = scanner.nextLine();
            Thread thread = map.get(Integer.valueOf(commond));
            thread.interrupt();
        }


    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值