重入锁 ReentrantLock 源码浅析(二)

276 阅读3分钟

这次补全上次 Lock 留下的坑,把剩下的源码补充完事。

AQS 释放锁逻辑

release(int arg)

释放锁的调用关系

ReentrantLock.unlock() —> sync.release() —> AQS 提供的 relsese

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

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

protected final boolean tryRelease(int releases) {
    // 减去释放的值..
    int c = getState() - releases;
    // 如果当前线程并未持锁,直接异常。
    if (Thread.currentThread() != getExclusiveOwnerThread())
      throw new IllegalMonitorStateException();
    // 当前线程持有锁
    boolean free = false;//是否已经完全释放锁
    // 只有当前线程的 state 值为 0,才是完全释放锁
    if (c == 0) {
      free = true;
      setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

unparkSuccessor(Node node)

AQS 中的 unparkSuccessor。

唤醒当前节点的下一个节点。

    private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
        if (ws < 0) // 改成 0 的原因:因为当前节点已经完成唤醒后继节点的任务了。
            compareAndSetWaitStatus(node, ws, 0);
        // 当前节点的第一个后继节点
        Node s = node.next;
        /*
         *什么时候 s 等于 null 呢
         *1.当前节点就是 tail 节点时
         *2.当节点未入队完成时
         *  重述一遍入队过程:
         *    ① 设置新节点的 prev 指向 pred
         *    ② cas 这只新节点为 tail
         *    ③ Pred.next 指向新节点(此处未完成)
         *  当 ③ 还未完成时当前节点调用 release, 那么你拿到的 node.next 为 Null
         *需要找到可以唤醒的节点
         */
        //接下来看条件二
        //如果进入条件二,那么说明 s != null
        //说明 s 节点为取消状态,那么需要找一个合适的可以被唤醒的节点
        if (s == null || s.waitStatus > 0) {
            // 查找可以被唤醒的节点
            s = null;
            // 从队尾遍历,会找到一个离当前 node 最近的一个可以被唤醒的 node,
            // node 可能找不到,node 可能是 null
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        // 如果找到合适的可以被唤醒的 node,则唤醒..找不到 什么也不做。
        if (s != null)
            LockSupport.unpark(s.thread);
    }

总的来说一下上面三个方法。首先如果我们调用 ReentrantLock 的 unlock 方法,实际上我们就是调用 AQS的 release 方法。在 release 方法里我们首先会去尝试释放锁,什么时候算完全释放锁呢,当前线程的 state = 0的时候。如果当前 state 值为 0,那么如果当前线程不是空并且不是尾节点的话我们就会尝试唤醒后继节点。去阻塞队列里找到距离当前节点最近的,并且唤醒 waitStatus 小于等于 0 的节点。以上就是释放锁的大体逻辑。

AQS 响应中断出队

cancelAcquire(Node node)

取消指定 node 参与竞争

private void cancelAcquire(Node node) {
    if (node == null)
      return;
        // 因为已经取消排队了,所以 node 内部关联的当前线程,置为 NULL 就好了
    node.thread = null;
    Node pred = node.prev;
    while (pred.waitStatus > 0)
      node.prev = pred = pred.prev;
    Node predNext = pred.next;
    // 将当前node 状态设置为 取消状态 1
    node.waitStatus = Node.CANCELLED;
    /*
     * 当前取消排队的node 所在队列的位置不同,执行的出队策略是不一样的,一共分为三种情况:
     * 1.当前 node 是队尾  tail -> node
     * 2.当前 node 不是 head.next 节点,也不是 tail
     * 3.当前 node 是 head.next 节点
     */
    //条件一: node == tail  当前 Node 是队尾
    //条件二: 成功的话,说明修改 tail 完成
    if (node == tail && compareAndSetTail(node, pred)) {
      // 如果当前node 是队尾的话肯定要将 node 与前继节点断开然后尾指针指向前继节点
      compareAndSetNext(pred, predNext, null);
    } else {
      int ws;
      // 当前 node 不是 head.next 节点,也不是 tail
      if (pred != head &&
          // 条件2.1:成立,说明 node 的前驱状态是 signal 状态
          //         不成立:前驱状态可能是 0 ,也可能是 1(前驱也取消排队..)
          // 条件2.2:假设前驱状态 <= 0,则设置前驱状态为 Signal 状态..表示要唤醒后继节点。
          //if 里面:就是让 pred.next -> node.next,所以需要保证 pred 节点状态为 signal 状态
          ((ws = pred.waitStatus) == Node.SIGNAL ||
           (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
          pred.thread != null) {
        // 出队:pred.next -> node.next 节点后,当 node.next 节点被唤醒后
        // 调用 shouldParkAfterFailedAcquire 会让 node.next 节点越过取消状态的节点
        // 完成真正出队
        Node next = node.next;
        if (next != null && next.waitStatus <= 0)
          compareAndSetNext(pred, predNext, next);
      } else {

        //当前 node 是 head.next 节点
        // 后继节点唤醒后,会调用 shouldParkAfterFailedAcquire 会让 node.next 节点越过取消状态的节点
        // 队列的第三个节点 会 直接与 head 建立 双重指向的关系:head.next -> 第三个 node 中间就是被出队                 //的 head.next 第三个 node.prev -> head(下面有简图)
        unparkSuccessor(node);
      }
      // 指向自己,出队
      node.next = node; // help GC
    }
}

AQS 非公平锁

顺带说几嘴非公平锁,如果你理解了公平锁,非公平锁只是稍有不同。

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

    final void lock() {
        // 上来就尝试加锁,也不管队列里有没有其他线程在等待,也不管当前状态有没有加锁。
        if (compareAndSetState(0, 1))
            // 抢占成功设置独占
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }

    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }

    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            // 如果当前状态为0,它还是不判断当前队列里是否有等待线程,直接尝试抢锁
            if (compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        // 没成功查看有没有重入
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
}
// 接下来都一样,该入队列入队列,该抢占资源抢占资源
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}