JUC 同步工具类之 AQS同步框架详解

188 阅读5分钟

AQS原理分析

什么是AQS

java.util.concurrent包中的大多数同步器实现都是围绕着共同的基础行为,比如等待队列、条件队列、独占获取、共享获取等,而这些行为的抽象就是基于 AbstractQueuedSynchronizer(简称AQS)实现的,AQS是一个抽象同步框架,可以用来实现一个依赖状态的同步器
JUC中提供的大多数的同步器如Lock, Latch,都是基于AQS框架来实现的

  • 一般是通过定义一个非公开的内部类辅助类继承AQS,用于实现同步器的同步属性。
  • 同步器所有调用都映射到内部辅助类对应的方法。

比如CountDownLatch内部实现的同步辅助类image.png

AQS的一些实现类: image.png

AQS框架实现思想

既然AbstractQueuedSynchronizer 带有queue,那一定和队列密不可分了,其实AQS和synchornized一样,都是借助java管程模型实现的同步器。

v2-8f64f0cd2aa4aaedccedba20b4ed9693_r.jpg AQS定义两种队列 同步等待队列: 主要用于维护获取锁失败时入队的线程
条件等待队列: 调用await()的时候会释放锁,然后线程会加入到条件队列,调用 signal()唤醒的时候会把条件队列中的线程节点移动到同步队列中,等待再次获得锁。
公平/非公平则表示锁被释放后,新晋新线程可以不用排队就可以竞争并获取锁时,对应该图则是,当前线程释放锁后,会唤醒队列中后继节点的线程,同时新晋线程也可以竞争锁,如果新晋线程竞争锁成功,那么新晋线程即为头节点,失败则入队成为尾节点阻塞等待。

AQS框架中主要属性方法

AQS队列由内部类Node以链表的方式组成,先看下这个Node的主要属性。 首先一定会有上一个节点及下一个节点的指针,即prev,next;thread代表当前Node代表的线程;等待状态waitStatus:

  1. 值为0,初始化状态,表示当前节点在sync队列中,等待着获取锁。
  2. CANCELLED,值为1,表示当前的线程被取消;
  3. SIGNAL,值为-1,表示当前节点的后继节点包含的线程需要运行,也就是unpark;
  4. CONDITION,值为-2,表示当前节点在等待condition,也就是在condition队列 中;
  5. PROPAGATE,值为-3,表示当前场景下后续的acquireShared能够得以执行;

AQS主要属性:head,tail 表示AQS队列的头尾节点,如果获取不到锁入队,则添加至尾节点后。 state 表示锁定状态,通过cas竞争此共享变量获取/释放锁,State可以获取,设置以获取当前状态及更改状态; AQS具备的特性:阻塞等待队列 共享/独占 公平/非公平 可重入 允许中断
AQS定义两种资源共享方式 Exclusive-独占,只有一个线程能执行,如ReentrantLock Share-共享,多个线程可以同时执行,如Semaphore/CountDownLatch
AQS定义两种队列 同步等待队列: 主要用于维护获取锁失败时入队的线程
条件等待队列: 调用await()的时候会释放锁,然后线程会加入到条件队列,调用 signal()唤醒的时候会把条件队列中的线程节点移动到同步队列中,等待再次获得锁
AQS 定义了5个队列中节点状态:

不同的自定义同步器竞争共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。自定义同步器实现时主要实现以下几种方法:
isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。 tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但 没有剩余可用资源;正数表示成功,且有剩余资源。
tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待 结点返回true,否则返回false。

AQS主要代码详解

构建同步等待队列方法:

/**
 * Creates and enqueues node for current thread and given mode.
 *
 * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
 * @return the new node
 */
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;
        // 获取尾节点,CAS成功置为尾节点(防止并发)
        if (compareAndSetTail(pred, node)) {
            // 则把新节点添加链表最后
            pred.next = node;
            return node;
        }
    }
    
    // 如果头节点还未初始化或者compareAndSetTail失败走enq逻辑
    enq(node);
    return node;
}


/**
 * Inserts node into queue, initializing if necessary. See picture above.
 * @param node the node to insert
 * @return node's predecessor
 */
private Node enq(final Node node) {
    // 自旋逻辑,乐观锁,自旋直至成功
    for (;;) {
        Node t = tail;
        // 头节点未初始化则设置头节点且头尾相同
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            // 这里对于上面compareAndSetTail失败逻辑,只是变为自旋直到成功
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}