【Java】 图解 AQS

1,117 阅读7分钟

这是我参与8月更文挑战的第7天,活动详情查看:8月更文挑战

一、前言

AbstractQueuedSynchronizer 简称 AQS,抽象队列同步器

它是 JUC 包实现同步的基础工具。

AQS 是 抽象类, 内置自旋锁实现的同步队列, 封装入队和出队的操作, 提供独占、共享、中断等特性的方法。

AQS 中, 定义了一个volatile int state变量作为共享资源:

  1. 如果线程获取资源失败, 则进入同步 FIFO 队列中等待
  2. 如果成功获取资源就执行临界区代码
  3. 执行完释放资源时, 会通知同步队列中的等待线程来获取资源后出队并执行

简单概括AQS 利用 CAS 维护状态, 利用 LockSupport来对线程进行操作。


利用 AQS 类的主要步骤:

  1. 第一步:新建一个自己的线程协作工具类,在内部写一个 Sync 类,该 Sync 类继承 AbstractQueuedSynchronizer,即 AQS
  2. 第二步:想好设计的线程协作工具类的协作逻辑,在 Sync 类里,根据是否是独占,来重写对应的方法。如果是独占,则重写 tryAcquiretryRelease 等方法;如果是非独占,则重写 tryAcquireSharedtryReleaseShared 等方法;
  3. 第三步:在自己的线程协作工具类中,实现获取/释放的相关方法,并在里面调用 AQS 对应的方法,如果是独占则调用 acquirerelease 等方法,非独占则调用 acquireSharedreleaseSharedacquireSharedInterruptibly 等方法。

AQS 实现原理

AQS 里最重要的两个东西:

  • state:表示状态,0 表示未加锁、1 表示已加锁
  • 队列:挂起的线程存放的位置
  1. AQS 初始,如图:

concurrent-AQS-场景1.png

  1. 线程1 和 线程2 并发执行,加锁,如图:

concurrent-AQS-场景2.png

  1. 线程1 执行完成,如图:

concurrent-AQS-场景3.png

  1. 线程3 加入,公平锁与非公平锁: concurrent-AQS-场景4.png



二、问题

(1)为什么需要 AQS

  1. 相对与 synchronized 基于 JVM, AQS 更加轻量化, 更易掌控
  2. AQS 底层依靠 CAS, 相对于挂起线程(切换上下文), 有时这个相对快速, 性能较好

(2)如何加锁

其实看上面的原理,就会明白,加锁主要两步:

  1. 判断 state,更新 state = 1

  2. 设置 AQS 的线程为当前线程

    其他线程,则会进入队列,并挂起


(3)如何释放锁

释放锁,理所当然是加锁的逆过程嘛:

  1. 更新 state
  2. 更新 AQS 的线程为 null
  3. 唤醒队列中队头的线程

如何唤醒队头的元素?

使用LockSupport.unpark(thread) 方法。

队头的元素唤醒之后是如何重新尝试加锁的呢?

  1. 先从阻塞状态中恢复
  2. 之后继续走 for(;;) 循环,从头开始判断
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this); // 1. 线程被阻塞在这
        // 2. 当被 LockSupport.unpark(thread) 之后
        // 会从这边继续运行
        return Thread.interrupted();
    }

(4)state 可以大于 1嘛?

可以的。

重入锁,state的值就会增加。

当然,释放的时候,也需要减少。



三、设计思想

主要动作:

  1. CAS 维护 state
  2. LockSupport 操作线程

同时, 采用了模板模式

子类可以通过实现 protected 方法, 自定义实现自己的操作逻辑, 从而对 state 进行操作。

下面列举一些常见需要子类实现的方法:

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


四、源码解析

主要从四个方面: Node、独占锁、共享锁、 LockSupport,

共享锁 与 独占锁的区别: 是否被多个线程

(0) Node 队列节点

每一个 Node 都持有一个线程

static final class Node{
    
    /*当前node对象的等待状态,注意该状态并不是描述当前对象而是描述下一个节点的状态,
     * 从而来决定是否唤醒下一个节点,该节点总共有四个取值:
     * a. CANCELLED = 1:因为超时或者中断,结点会被设置为取消状态,被取消状态的结点不应该去竞争锁,
     * 只能保持取消状态不变,不能转换为其他状态。处于这种状态的结点会被踢出队列,被GC回收;
     * b. SIGNAL = -1:表示这个结点的继任结点被阻塞了,到时需要通知它; 
     * c. CONDITION = -2:表示这个结点在条件队列中,因为等待某个条件而被阻塞;
     * d. PROPAGATE = -3:使用在共享模式头结点有可能牌处于这种状态,表示锁的下一次获取可以无条件传播;
     * e. 0: None of the above,新结点会处于这种状态。
     * 
     * 非负值标识节点不需要被通知(唤醒)。
    */
    volatile int waitStatus;
​
    //当前节点的上一个节点,如果是头节点那么值为null
    volatile Node prev;
    //当前节点的下一个节点
    volatile Node next;
    
    //与Node绑定的线程对象
    volatile Thread thread;
    
    //下一个等待条件(Condition)的节点,由于Condition是独占模式,因此这里有一个简单的队列来描述Condition上的线程节点。
    Node nextWaiter;
​
}

(1) 独占锁

流程,如图:

2021-08-0717-49-50.png

  1. acquire() 获取锁
public final void acquire(int arg) {
    // 1. 尝试获取同步状态, 成功就退出
    // 2. 若失败, 则添加至队尾
    if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
​
private Node addWaiter(Node mode) {
    // 1. 将当前线程构建成Node类型
    Node node = new Node(Thread.currentThread(), mode);
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    // 2. 当前同步队列尾节点为null,说明当前线程是第一个加入同步队列进行等待的线程
    enq(node);
    return node;
}
​
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
            //1. 构造头结点
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            // 2. 尾插入,CAS操作失败自旋尝试
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}
​
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        
        // 自旋的过程
        for (;;) {
            // 1. 获得当前节点的先驱节点
            final Node p = node.predecessor();
            // 2. 当前节点能否获取独占式锁                  
            // 2.1 如果当前节点的先驱节点是头结点并且成功获取同步状态,即可以获得独占式锁
            if (p == head && tryAcquire(arg)) {
                //队列头指针用指向当前节点
                setHead(node);
                //释放前驱节点
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            // 2.2 获取锁失败,线程进入等待状态等待获取独占式锁
            if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
​
// 在获取失败后进行挂起
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
       
        return true;
    if (ws > 0) {
       
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
       
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}
​
private final boolean parkAndCheckInterrupt() {
    //使得该线程阻塞
    LockSupport.park(this);
    return Thread.interrupted(); // 返回是否阻塞
}
  1. release() 释放锁
public final boolean release(int arg) {
    // tryRelease() 是各实现类需要实现的方法
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
​
// 每一次锁释放后就会唤醒队列中该节点的后继节点所引用的线程,从而进一步可以佐证获得锁的过程是一个FIFO(先进先出)的过程。
private void unparkSuccessor(Node node) {
​
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
​
    //头节点的后继节点
    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)
        //后继节点不为null时唤醒该线程
        LockSupport.unpark(s.thread);
}
​
  1. acquireInterruptibly 可中断式获取锁
public final void acquireInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (!tryAcquire(arg))
        //线程获取锁失败
        doAcquireInterruptibly(arg);
}
​
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);
    }
}
​
  1. tryAcquireNanos() 超时等待式获取锁

即, 调用lock.tryLock(timeout, TimeUnit)方式

public final boolean tryAcquireNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    return tryAcquire(arg) ||
        //实现超时等待的效果
        doAcquireNanos(arg, nanosTimeout);
}
​
​
private boolean doAcquireNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    if (nanosTimeout <= 0L)
        return false;
    //1. 根据超时时间和当前时间计算出截止时间
    final long deadline = System.nanoTime() + nanosTimeout;
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            //2. 当前线程获得锁出队列
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return true;
            }
            // 3.1 重新计算超时时间
            nanosTimeout = deadline - System.nanoTime();
            // 3.2 已经超时返回false
            if (nanosTimeout <= 0L)
                return false;
            // 3.3 线程阻塞等待 
            if (shouldParkAfterFailedAcquire(p, node) &&
                nanosTimeout > spinForTimeoutThreshold)
                LockSupport.parkNanos(this, nanosTimeout);
            // 3.4 线程被中断抛出被中断异常
            if (Thread.interrupted())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
​

(2) 共享锁

流程如图:

2021-08-0717-50-12.png

相对于独占锁, 只是在方法中加了shared

  1. acquireShared() 获取共享锁
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}
​
private void doAcquireShared(int arg) {
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    // 当该节点的前驱节点是头结点且成功获取同步状态
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
​
  1. releaseShared() 释放共享锁
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}
​
​
private void doReleaseShared() {
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                unparkSuccessor(h);
            }
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}
​

(3) LockSupport

//来看upparkSuccessor()方法
private void unparkSuccessor(Node node) {
​
    //省略无关代码
    LockSupport.unpark(s.thread);
}
​
public static void unpark(Thread thread) {
    //省略无关代码
    if (thread != null)
        //委托给UNSAFE#unpark方法,这是一个本地方法
        UNSAFE.unpark(thread);
}