AQS的应用:ReentrantLock源码分析

AQS: AbstractQueuedSynchronizer,抽象队列同步器。用来实现锁或其他同步器组件的公共基础部分的抽象实现,是重量级基础框架及整个JUC体系的基石,用于解决锁分配给“谁”的问题。

常见的同步组件如ReentrantLock、ReentrantReadWriteLock、CountdownLatch等都是基于AQS实现的(通过内部类extend AQS)。

AQS内部包含一个CLH变体队列,抢不到锁的线程将会被封装成一个Node并进入此队列,而此处的锁实际上就是state变量(volatile类型)。一旦进入队列,线程将会park,等待后续的unpark
在这里插入图片描述
ReentrantLock有两种实现方式:公平锁、非公平锁,由于非公平锁包含公平锁相关逻辑,此处以非公平锁的源码来进行讲解。

假设队列刚开始时为空,state资源也没有占用,此处有三个线程A、B、C依次调用lock.lock()方法

Lock lock = new ReentrantLock();
// 线程A
lock.lock();

// 线程B
lock.lock();

// 线程C
lock.lock();

线程A

针对线程A而言,直接在这一步就拿到锁返回了。
在这里插入图片描述

final void lock() {
    // 非公平锁体现在此处,首先抢一把,抢不到才进入队列park
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

针对B、C而言,他们将以Node的方式进入队列,效果图如下:

线程B

针对线程B而言,会依次执行if里面的三个方法,最后在acquireQueued()方法中park。
在这里插入图片描述

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
  1. 执行tryAcquire(),并返回false
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    // 针对线程B而言,由于state被A占用,此处c==1
    if (c == 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;
}
  1. 执行addWaiter()方法,并继而执行enq()方法
private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    // 此时tail==null,因为pred也为null
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}

private Node enq(final Node node) {
    // 线程B会执行for循环两次,分别走了if...else...两个分支
    for (;;) {
        // 此处将t指向tail的地址,是为了确保在下面CAS之前如果其它线程设置了tail可以自旋重试
        Node t = tail;
        if (t == null) {
            // 此处生成了一个虚拟的头结点(也就是说FIFO队列的头结点是个虚拟的空节点)
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}
  1. 执行acquireQueued()方法,B将在执行第2次for循环后被LockSupport.park(this)阻塞
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            // B线程:head节点的后一个节点会tryAcquire锁,但会失败
            // C线程:它的prev节点是B,不会tryAcquire(也就是说只要进入FIFO队列了,就是公平锁了)
            // 当抢到锁以后,它会替换自己前节点(header节点)的位置
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            // 线程B会执行2次for循环,第二次的时候会进入parkAndCheckInterrupt()
            // 且通过方法LockSupport.park(this)被阻塞(线程C也是如此)
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    // 默认的waitStatus==0,将会进入else分支,并将waitStatus改为Node.SIGNAL
    // 上层方法的for语句还会再执行,下次进来时就会返回true了
    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;
}

private final boolean parkAndCheckInterrupt() {
    // 从park方法返回有两种情况:unpark和被interrupt,如果是后者就不能再去抢锁了
    LockSupport.park(this);
    // Tests whether the current thread has been interrupted. 
    // The interrupted status of the thread is cleared by this method. 
    return Thread.interrupted();
}

线程C

线程C其它流程与B一致,只是addWaiter()有点不一样。
在这里插入图片描述

private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    // 由于tail此时已经存在了,故直接将此node添加到tail节点
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}

线程A释放锁

最后,当线程A执行完业务,调用lock.unlock()方法方法后释放资源。

public final boolean release(int arg) {
    // 尝试设置state-1,由于只能由持有锁的线程设置,故不存在并发问题,无需CAS
    // 由于是可重入锁,故state的值可能>1
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            // 唤醒header的后续节点,让其从park方法处返回
            unparkSuccessor(h);
        return true;
    }
    return false;
}

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;
    // 如果s的后续节点为null或者CANCEL了,就要从tail开始找符合条件的节点
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                // 注意此处并没有return
                // 一直找到离header最近的waitStatus<=0的节点
                s = t;
    }
    if (s != null)
        // 此时线程B从LockSupport.park(this)被唤醒,继续执行后续流程,理论上如果没有新的线程抢锁,就能拿到锁(只能针对队列中FIFO,header的后续节点获取锁)
        LockSupport.unpark(s.thread);
}

为什么unparkSuccessor()要从tail开始呢?这是为了避免出现并发异常,在前面enq()中,当线程B刚好成为tail,而还未执行if代码块时,此时的状态如下。如果不从tail往前,那么由于head的next为null,将会漏掉该Node。而可以发现的是enq()方法是先执行了node.prev = t再进行CAS的,故从后往前就不会有问题。
在这里插入图片描述

private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) {
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            // 此处只要CAS成功,就肯定建立了上图中的连接,但可能next未建立
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

异常情况

以上是正常流程,而如果上述流程失败,failed=false因为某种异常未执行,将会进入finally的if分支。
在这里插入图片描述
比如tryAcquire()方法抛throw new Error(“Maximum lock count exceeded”)异常,那么就会进入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.
    // 既然已经failed了,此处就是CANCELLED
    node.waitStatus = Node.CANCELLED;

    // If we are the tail, remove ourselves.
    // 此时被cancel的节点恰好是tail E,节点D将成为tail
    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.
        // 中间节点的CACEL,此部分代码在AQS,可能是需要兼容其它的实现类,故有点复杂
        int ws;
        // 非header的下一个节点,直接指向node的下一个节点(中间可能会跨越多个CANCEL的节点)
        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 {    
            // 如果node前面已经是header节点了,既然node已经失败了,那就唤醒下一个节点
            unparkSuccessor(node);
        }

        node.next = node; // help GC
    }
}

可以分三种情况调用cancelAcquire()分析:

  • 节点E:直接把D的next置为null
  • 节点D取消:C.next -> E
  • 节点C、D取消:B.next -> E
    在这里插入图片描述
    参考:尚硅谷JUC:https://www.bilibili.com/video/BV1ar4y1x727?vd_source=2bea06318ac5c165ddf39315231c3508
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值