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

文章目录

      • [什么是 AQS](#什么是 AQS)
      • [底层数据结构------ CLH 队列](#底层数据结构—— CLH 队列)
      • 入队和出队
      • 状态标志位
      • [AQS 的代码设计思路](#AQS 的代码设计思路)
      • [AQS 提供的钩子方法](#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),该节点的线程将重新尝试获取锁。如果成功获取锁,该线程将继续执行。

java 复制代码
  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的队首节点和队尾节点都是懒加载的。

java 复制代码
    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()

java 复制代码
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

相关推荐
考虑考虑1 天前
Jpa使用union all
java·spring boot·后端
用户3721574261351 天前
Java 实现 Excel 与 TXT 文本高效互转
java
浮游本尊1 天前
Java学习第22天 - 云原生与容器化
java
渣哥1 天前
原来 Java 里线程安全集合有这么多种
java
间彧1 天前
Spring Boot集成Spring Security完整指南
java
间彧1 天前
Spring Secutiy基本原理及工作流程
java
Java水解1 天前
JAVA经典面试题附答案(持续更新版)
java·后端·面试
洛小豆1 天前
在Java中,Integer.parseInt和Integer.valueOf有什么区别
java·后端·面试
前端小张同学1 天前
服务器上如何搭建jenkins 服务CI/CD😎😎
java·后端
ytadpole1 天前
Spring Cloud Gateway:一次不规范 URL 引发的路由转发404问题排查
java·后端