Java 并发编程 —— AQS 抽象队列同步器

什么是 AQS

AQS 是 JUC 提供的一个用于构建锁和同步容器的基础类,用于减少由于无效争夺导致的资源浪费和性能恶化。JUC 包内的许多类都是基于 AQS 构建, 例如 ReentrantLock、Semaphore、CountDownLatch、ReentrantReadWriteLock、FutureTask 等。AQS 解决了在实现同步容器时设计的大量细节问题。

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
}

AQS 的核心思想是, 如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制。

这个机制是基于 CLH 锁 (Craig, Landin, and Hagersten locks) 实现的, 即一个虚拟的双向队列。AQS 将每条请求共享资源的线程封装成一个 CLH 队列锁的一个结点(Node)来实现锁的分配。

底层数据结构—— CLH 队列

AQS(AbstractQueuedSynchronizer)内部维护的确是一个 FIFO(先进先出) 的双向链表结构,用于管理线程的同步状态。这个双向链表的结构特点是,能够从任意节点很方便地访问它的前驱和后继节点,从而在需要唤醒某个线程时,可以快速地找到并操作相关节点。

AQS 的 Node 节点是封装了线程的基本单元,它们存储线程的信息以及线程的状态。每个线程在争抢锁失败后,会被封装成一个 Node 节点,并添加到队列的尾部。当持有锁的线程释放锁时,它会唤醒队列中第一个等待的节点(FIFO),该节点的线程将重新尝试获取锁。如果成功获取锁,该线程将继续执行。

  private transient volatile Node head;
 
  private transient volatile Node tail;
 
  abstract static class Node {
    
    
        volatile Node prev;       // initially attached via casTail
        volatile Node next;       // visibly nonnull when signallable
        Thread waiter;            // visibly nonnull when enqueued
        volatile int status;      // written by owner, atomic bit ops by others
        ...
  }
 

在这里插入图片描述

入队和出队

每当线程通过 AQS 获取锁失败时,线程将被封装成一个 Node 节点,通过 CAS 原子操作插入队列尾部。当有线程释放锁时,AQS 会尝试让队首的后驱节点占用锁。AQS的队首节点和队尾节点都是懒加载的。

    final void enqueue(Node node) {
    
    
        if (node != null) {
    
    
            for (;;) {
    
    
                Node t = tail;
                node.setPrevRelaxed(t);        // avoid unnecessary fence
                if (t == null)                 // initialize
                    tryInitializeHead();
                else if (casTail(t, node)) {
    
    
                    t.next = node;
                    if (t.status < 0)          // wake up to clean link
                        LockSupport.unpark(node.waiter);
                    break;
                }
            }
        }
    }

在这里插入图片描述

状态标志位

state 用于表示同步状态,volatile 确保多线程环境下,对 state 的修改能立即反映到其他线程中,常用于简单的状态标记或轻量级的同步控制。在 ReentrantLock 类中,state 就用于实现可重入锁。

同时, AQS 提供了 CAS 算法实现的修改方法 compareAndSetState()

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
    
    
    
    /**
     * The synchronization state.
     */
    private volatile int state;
    
     protected final int getState() {
    
    
        return state;
    }
    
    protected final void setState(int newState) {
    
    
        state = newState;
    }
    
    // CAS 修改方法
    protected final boolean compareAndSetState(int expect, int update) {
    
    
        return U.compareAndSetInt(this, STATE, expect, update);
    }
    
}

AQS 的代码设计思路

AQS 出于“分离变与不变”的原则,基于模板模式实现AQS 为锁获取、锁释放的排队和出队过程提供了一系列的模板方法。由于 JUC 的显式锁种类丰富,因此 AQS 将不同锁的具体操作抽取为钩子方法,供各种锁的子类(或者其内部类)去实现。

显示锁和 AQS之间的关系为组合关系。

在这里插入图片描述

AQS 提供的钩子方法

AQS 针对共享锁和独享锁这两种资源共享方式提供了不同的模板方法,定义了不同的钩子方法:

  • tryAcquire(int):独占锁钩子,尝试获取资源。若成功则返回true,若失败则返回false。
  • tryRelease(int):独占锁钩子,尝试释放资源。若成功则返回true,若失败则返回false。
  • tryAcquireShared(int):共享锁钩子,尝试获取资源,负数表示失败;0表示成功,但没有剩 余可用资源;正数表示成功,且有剩余资源。
  • tryReleaseShared(int):共享锁钩子,尝试释放资源。若成功则返回true,若失败则返回false。
  • isHeldExclusively():独占锁钩子,判断该线程是否正在独占资源。只有用到condition条件 队列时才需要去实现它。

除了钩子方法以外, AQS 以外的其他方法基本都是 final

参考资料

《 极致经典(卷2):Java高并发核心编程(卷2 加强版) -特供v21-release》

AQS 详解 | JavaGuide

猜你喜欢

转载自blog.csdn.net/weixin_44814196/article/details/142396419