AQS 源码分析

136 阅读5分钟

一、简介

  • AQS,即AbstractQueuedSynchronizer,抽象队列同步器。
  • 简单来说,就是通过AQS对多个线程的状态进行管理,park状态、unpark状态之间进行转换。
  • AQS内部通过双向链表实现,线程以先进先出的顺序执行。
  • JUC的多个应用类,内部都是通过AQS实现的。
  • 以下以ReentrantLock为例进行讲解。

二、ReetrantLock

image.png

  • ReentrantLock内部有三个内部类,分为公平锁和非公平锁。而它们的基类都是SyncSync的父类是AQS
  • ReentrantLock默认使用的是非公平锁,即刚进来的线程有可能因为幸运,一进来就插队抢到执行权,而队列的头结点也有可能动作比较慢被抢执行权。
  • Sync有一个最重要的抽象方法,分别由NonfairSyncFairSync予以不同的方式实现。
    abstract void lock();
    

1. lock()

  • 而两个不同最大的不同就是,是否让刚进来的线程通过CAS尝试获取执行权。
// 非公平锁
static final class NonfairSync extends Sync {
    //...
    /* 执行锁操作 */
    final void lock() {
        if (compareAndSetState(0, 1))  // 尝试通过CAS设置状态
            setExclusiveOwnerThread(Thread.currentThread());  // 如果进入成功,那就直接将自己线程作为执行线程
        else
            acquire(1); // 否则,则进入尝试获取状态
    }
    //...
 }
// 公平锁
static final class FairSync extends Sync {
    //...
    final void lock() {
        acquire(1);
    }
    //...
} 

2. compareAndSetState()

  • 到这值得一提的是,在NonfairSync中,compareAndSetState()state是什么意思。
  • 在AQS中,有一个成员属性是state,这个量标志着目前AQS正在处于什么工作状态。0表示没有线程在执行,1表示存在线程正在执行。
private volatile int state;
  • 因此对于NonfairSync中的compareAndSetState()也可以理解了,意思就是如果目前state0,就抢占先机,自己把它设置为1,然后再进去工作。

3. setExclusiveOwnerThread()

  • 如果设置成功之后,就直接通过setExclusiveOwnerThread()将自己的线程设置为执行的主线程,直接上位了。
  • 此方法在AbstractOwnableSynchronizer类中,是AQS的父类。
public abstract class AbstractOwnableSynchronizer
    implements java.io.Serializable {
    //...
    /* 目前执行的线程 */
    private transient Thread exclusiveOwnerThread;
    /* 设置 */
    protected final void setExclusiveOwnerThread(Thread thread) {
        exclusiveOwnerThread = thread;
    }
    /* 获取 */
    protected final Thread getExclusiveOwnerThread() {
        return exclusiveOwnerThread;
    }
}

image.png

4. acquire()

  • 这是AQS的方法
  • 如果没抢占到,线程就进入正常的操作中了。此方法在非公平锁和公平锁中都是一样的。
  • 此方法在AQS内
    • 首先尝试获取线程,tryAcquire()是重要的方法。
    • 尝试进入队列,首先通过addWaiter()生成一个队列节点Node,然后acquireQueued()放入队列,并阻塞等待。
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&  // 先尝试获取,万一在此期间成功了呢
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))  // 否则就进入队列,并将状态设置为独占锁
        selfInterrupt();
}

5. tryAcquire()

  • 这个方法非常常用,在往后的操作中也会使用到,表示尝试获取线程执行权。
  • 而该方法在AQS中是一个抽象方法,需要子类实现。
protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}
  • 在公平锁和非公平锁中实现内容都类似。
protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {  // 如果目前state处于0,表示没有线程在执行,可以进入。
        if (!hasQueuedPredecessors() &&  // 看看队列里面有没有要执行的线程,如果没有,赶紧进去
            compareAndSetState(0, acquires)) {  // 通过CAS设置,看看能不能抢得到
            setExclusiveOwnerThread(current);  // 将自己设置为要被执行的线程
            return true; // 返回true,表示执行完毕
        }
    }
    else if (current == getExclusiveOwnerThread()) {  // 如果当前执行线程与当前线程相同,表示就是一个可重入锁
        int nextc = c + acquires;  // 状态值叠加
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);  // 设置状态值
        return true;  // 直接返回true,表示执行完毕
    }
    return false;  // 否则,表示没有执行
}
  • 方法有两个最重要的尝试:
    • 如果目前队列没有要执行的线程且state0,就通过CAS占用;
    • 如果自己的线程和正在执行的线程是同一个线程,就表示这是一个可重入锁(也就是在lock()里面又lock()了一遍),在原来的state基础上加上自己的参数,而解锁的时候,需要像栈一样,将所有lock()都解掉。
  • 如果该方法返回true,上面的acquire()方法就表示执行结束了,该线程的处理也就完毕了。

6. addWaiter()

  • 如果上面的方法为false,则需要执行acquireQueued()方法了。不过在执行该方法之前,还需要执行一个添加节点的操作。
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;  // 获取尾节点
    if (pred != null) {  // 如果尾节点不为空,那就在尾节点后面接一个节点
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;  // 直接返回该节点
        }
    }
    enq(node);  // 如果尾节点为空,表示这是一个新的队列,需要添加头结点和尾节点,那就进入该方法吧
    return node;  // 操作完成之后,就可以返回了
}
  • 此方法一开始就新建一个Node,利用Node保存当前线程及模式(用于之后添加到队列中)。Node的相关信息,下一节介绍。
  • 然后尝试获取该队列的尾结点,如果存在,就将新节点添加到尾结点处。然后返回node
  • 如果目前尾节点还不存在,说明现在是一个新的队列,还需要进行一些操作,因此,就进入了enq()方法中。
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // 如果尾节点是空,那就新建一个空节点,让头和尾部都指向它
            if (compareAndSetHead(new Node()))  
                tail = head; 
        } else {
            node.prev = t;  // 指向尾节点
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}
  • 该方法很巧妙,做了一个循环结构,一开始tail==null,因此新建一个节点,并让头指针和尾指针都指向它。
  • 后来,让新的节点插入到尾节点后面,然后返回。
  • 至此,完成了Node的入队操作。

7. Node

  • 在此,插入一个简单的解释。
  • Node是用于封装线程及其属性的一个类,存放在AQS中。
static final class Node {
        /** 表示这是个共享锁,与ReetrantLock无关 */
        static final Node SHARED = new Node();
        /** 表示这是一个独占锁,就是当前的模式 */
        static final Node EXCLUSIVE = null;
        // 目前等待状态的枚举
        static final int CANCELLED =  1;
        static final int SIGNAL    = -1;
        static final int CONDITION = -2;
        static final int PROPAGATE = -3;

        /**
         * Status field, taking on only the values:
         *   SIGNAL:     该节点被park阻塞了,需要通过unpack取消阻塞
         *   CANCELLED:  该节点已经被取消了
         *   CONDITION:  该节点处于一个很尴尬的状态,不能执行,需要被重置为0才能进入执行
         *   PROPAGATE:  该节点应该被传播到其他节点,应该暂时无关
         *   0:          最基础的状态,初始状态
         */
         // 这是一个很重要的状态,表示该节点的等待状态
        volatile int waitStatus;

        /* 前一个节点 */
        volatile Node prev;

        /* 后一个结点*/
        volatile Node next;

        /* 保存的线程 */
        volatile Thread thread;

        /* 应该是共享锁才会用到 */
        Node nextWaiter;
}

8. acquireQueued()

  • 上两节,新生成了一个队列的节点,并放入到队列,现在就可以尝试排队等待了。
    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) &&  // 如果上一个条件不满足,就会进入阻塞park状态,直到有人唤醒
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
  • 这段代码有两个关键的地方,都通过注释标注了。
    • 如果自己是下一个结点,且获取执行成功,那就将自己设为下一个头结点
    • 如果上一个条件不满足,就会进入shouldParkAfterFailedAcquire()方法,修改等待状态为阻塞态;并且parkAndCheckInterrupt()方法,进入真正的阻塞态。

9. shouldParkAfterFailedAcquire()

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;  // 获取节点的state
    if (ws == Node.SIGNAL)
        /* 如果已经不是第一次进来了,那就直接返回true就好了。SIGNAL表示目前处于阻塞状态 */
        return true;
    if (ws > 0) {
        /* 如果该节点已经被设为CANCEL取消状态了,那就删除它 */
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        /* 表示这个节点还是第一次来,那就设置一下它的等待状态,把它设置为SIGNAL */
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

10. parkAndCheckInterrupt()

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);  // 阻塞状态,直到有人唤醒
    return Thread.interrupted();
}
  • 至此,完成了该线程尝试获取lock()的全部过程,后面就是等待解锁的过程了。

11. unlock()

  • 其他的线程想被解锁,必须要有一个线程解开锁。
  • 这是ReentrantLock的解锁操作
public void unlock() {
    sync.release(1);
}

12. release()

  • 在AQS中,有解锁的具体操作。而这个方法又调用一个tryRelease()方法,尝试释放,如果释放已经成功了,那就unparkSuccessor()唤醒下一个执行的线程。
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

13. tryRelease()

  • Sync中有它的具体实现。该方法主要是用于解掉当前锁的状态,但至于能不能彻底释放,让其他线程进来,还要看这个锁外面还有没有锁。
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;  // 将state减去当前的值
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;  // free状态
    if (c == 0) {  // 如果state已经减到0了,表示目前处于可以释放的状态了。如果不是,说明这是个可重入锁,还有其他锁需要解了才能用
        free = true;  
        setExclusiveOwnerThread(null);  // 把自己从执行线程中删去
    }
    setState(c);  // 保存状态
    return free;  // 返回现在是否为free状态
}

14. unparkSuccessor()

  • 该方法用于唤醒下一个执行线程
private void unparkSuccessor(Node node) {
    /* ws<0,都是表示一些工作方面的状态,现在工作已完成了,把它修改回0 */
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    /* 查找下一个节点,并且保证该节点不是被CANNEL的 */
    Node s = node.next;  // 下一个结点
    if (s == null || s.waitStatus > 0) {  // 如果下一个结点为0或者为CANCELLED状态,就进来
        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);
}