并发编程之AQS

164 阅读6分钟

前言

AQS全称是AbstractQueuedSynchronize,是java JUC的核心,java现有的ReentrantLock、CoundDownLatch以及Semaphore都是基于AQS实现的,其内部又可分为独占和共享、公平和非公平

AQS工作原理

在AQS中维护着一个使用volatile修饰的全局变量state,使用它来标识同步状态,当state为0时代表着没有线程占有资源,不为0时意味着有线程占有资源;而后其他想要获取资源的线程需要先进入同步队列等待锁的释放。其内部使用Node内部类构建了一个FIFO(先进先出)的同步队列,将等待获取锁的线程放入队列中(本质上使用的是一种链式结构,标记有头尾结点head-tail)。
同时AQS还使用ConditionObject内部类构建了等待队列,当Condition调用await()方法后,会将调用线程加入等待队列当中,而当Condition调用signal,会将线程从等待队列中转移到同步队列

// 共享同步队列的头部
private transient volatile Node head;
// 共享同步队列的尾部
private transient volatile Node tail;
// 同步状态标识
private volatile int state;

上述代码使用的Node节点是每一个获取锁资源的线程的封装体,其内部包括线程本身的状态,以及其前驱节点和后继节点,其代码构成如下

static final class Node {
    // 标明为共享模式(Semaphore、读锁RaadLock等就是基于该模式实现)
    static final Node SHARED = new Node();
    // 独占模式(ReentranLock基于该模式实现)
    static final Node EXCLUSIVE = null;
    // 线程状态标识-结束状态(该模式下节点需要从同步队列中取消)
    static final int CANCELLED =  1;
    // 线程状态标识-等待唤醒状态(该状态下,其前驱节点如果释放或被取消将会通知该节点的线程执行)
    static final int SIGNAL    = -1;
    // 线程状态标识-Condition条件状态(该状态下,节点处于等待队列中,节点的线程等待condition条件,当调用Condition的signal()方法后,其将从等待队列转移到同步队列中)
    static final int CONDITION = -2;
    // 在共享模式中使用表示获得的同步状态会被传播(在共享模式中,标识为该状态的节点的线程处于可运行状态)
    static final int PROPAGATE = -3;
    
    // 等待状态,包含CANCELLED、SIGNAL、CONDITION、PROPAGATE
    volatile int waitStatus;

    // 同步队列前驱节点
    volatile Node prev;

    // 同步队列后继节点
    volatile Node next;

    // 获取锁资源的线程
    volatile Thread thread;

    // 等待队列中的后继节点
    Node nextWaiter;

    // 判断是否为共享模式
    final boolean isShared() {
        return nextWaiter == SHARED;
    }

    // 前驱节点获取
    final Node predecessor() throws NullPointerException {
        Node p = prev;
        if (p == null)
            throw new NullPointerException();
        else
            return p;
    }
    // 省略代码...
}

ReentranLock中的Sync类继承自AQS,其子类又分为FairSync和Nofairync也就是公平锁和非公平锁的实现者,AQS内部提供了独占模式下以及共享模式下释放和获取锁的方法,但其实现由子类完成

// 独占模式下获取锁
protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}
// 独占模式下释放锁
protected boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}
// 共享模式下获取锁
protected int tryAcquireShared(int arg) {
    throw new UnsupportedOperationException();
}
// 共享模式下释放锁
protected boolean tryReleaseShared(int arg) {
    throw new UnsupportedOperationException();
}
// 判断是否持有独占锁
protected boolean isHeldExclusively() {
    throw new UnsupportedOperationException();
}

ReentranLock中的非公平锁以及公平锁

非公平锁(NofairSync)

加锁

// 构造方法,ReentrantLock默认构造方法使用的就是非公平锁
public ReentrantLock() {
    sync = new NonfairSync();
}
// 带参构造方法,使用布尔值fair指定使用的是公平锁构造还是非公平锁构造
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

非公平锁的加锁

static final class NonfairSync extends Sync {
    final void lock() {
        // 执行CAS操作,修改同步状态获取锁资源,使用CAS保证原子性 
        if (compareAndSetState(0, 1))
            // 加锁成功,将独占锁线程修改为当前线程
            setExclusiveOwnerThread(Thread.currentThread());
        else
            // 修改失败,再次请求同步状态
            acquire(1);
    }

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

流程图如下

graph LR
A[lock]-->B[执行CAS操作,尝试修改同步状态]-->C{是否修改成功}
C--成功-->D[将独占线程修改为当前线程]
C--失败-->E[再次请求同步状态]

请求同步状态的方法内部主要逻辑是:1,进入方法后执行tryAcquire(arg)方法,此处由子类NofairSync中的tryAcquire(arg)方法实现;2, 使用addWaiter将线程封装成一个Node节点,将其加入到同步队列尾部;3,进行自旋,在观察时机等待条件满足时获取同步状态,然后退出自旋,由acquireQueued()方法执行

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

tryAcquire(arg)方法解析,由于调用的是NofairSync所以实际调用的是nonfairTryAcquire(int acqure)方法

final boolean nonfairTryAcquire(int acquires) {
    // 获取当前执行线程
    final Thread current = Thread.currentThread();
    // 获取同步器状态标识值
    int c = getState();
    // 判断状态标识值是否为0,并尝试再次修改同步状态
    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;
}

在tryAcquire执行完成之后,如果成功,那么可以直接能够获取锁,也就没有后续的逻辑了,如果没有加锁成功,那么需要继续执行addWaiter(Node.EXCLUSIVE)进行线程封装以及acquireQueued(Node node)获取同步状态的操作

private Node addWaiter(Node mode) {
    // 将线程封装成Node节点
    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;
        // 使用CAS操作,替换尾节点
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    // 如果尾第一次加入或者CAS操作没有执行成功,那么执行enq入队操作
    enq(node);
    return node;
}

enq方法解析

private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        // 如果尾节点为空则队列为空
        if (t == null) { // Must initialize
            // CAS操作创建头节点以及尾节点,此处new出来的Node节点,只是作为一个牵头节点
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            // 使用CAS向队尾加入新节点
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

将新节点放入同步队列之后,通过自旋,获取同步状态,其函数方法为acquiredQueued(Node node,arg)

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        // 阻塞挂起标识
        boolean interrupted = false;
        for (;;) {
            // 获取前驱节点
            final Node p = node.predecessor();
            // 如果p为头结点才尝试获取同步状态(此处获取的p为当前节点的前置节点)
            if (p == head && tryAcquire(arg)) {
                // 将node设置为头结点
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            // 如果前驱节点不是head,判断是否阻塞挂起线程,如果需要那么阻塞挂起,阻塞标识修改为true
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            // 如果同步状获取失败,结束该线程请求
            cancelAcquire(node);
    }
}

头节点设置(setHead(Node node))方法解析

private void setHead(Node node) {
    head = node;
    // 当前线程已经获取到了锁资源,所以没必要存储线程信息
    node.thread = null;
    // 当前节点已经成为头节点,不存在前置节点
    node.prev = null;
}

如果前置节点不是头结点,那么判断是否需要挂起,以及执行挂起逻辑

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    // 获取当前节点的状态
    int ws = pred.waitStatus;
    // 如果为等待唤醒状态,那么则返回true
    if (ws == Node.SIGNAL)
        return true;
    // 大于0说明是结束状态,遍历前驱节点,找到没有结束状态的节点,并删除所有状态为结束的节点
    if (ws > 0) {
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        // 如果该节点状态小于0但又不是SIGNAL状态,意味着该节点刚从条件队列中转移到同步队列,那么设置其为SIGNAL状态
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}


private final boolean parkAndCheckInterrupt() {
    // 使用LOCKSupport的park方法挂起当前线程
    LockSupport.park(this);
    // 获取线程的中断状态
    return Thread.interrupted();
}

public static void park(Object blocker) {
    Thread t = Thread.currentThread();
    // 设置当前线程的监视器blocker
    setBlocker(t, blocker);
    // 调用native方法阻塞当前线程
    UNSAFE.park(false, 0L);
    // 置空blocker
    setBlocker(t, null);
}

释放锁

在reentrantLock中,释放锁需要手动显示的释放锁,即调用unlock()方法

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

// AQS->release()方法
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;
    // 判断状态是否为0,如果是则说明已经释放同步状态
    if (c == 0) {
        free = true;
        // 设置owner(锁持有的线程)为null
        setExclusiveOwnerThread(null);
    }
    // 更新同步状态
    setState(c);
    return free;
}

资源释放之后,唤醒后继节点的线程,即调用unparkSuccessor(h)方法

private void unparkSuccessor(Node node) {
    // 获取当前线程等待状态
    int ws = node.waitStatus;
    if (ws < 0)
        // 使用CAS置零当前线程所在节点状态
        compareAndSetWaitStatus(node, ws, 0);
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        //此处如果为空或者状态为结束状态,那么尾部向前遍历,找到状态不为结束的节点,将其赋值给s
        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);
}

公平锁FairSync的加锁

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        // 在公平锁的实现中这里优先判断同步队列是否存在节点
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

参考资料