Java中的AQS

AQS(AbstractQueuedSynchronizer)是Java中一个抽象的队列同步器,用于实现并发组件如ReentrantLock、Semaphore等。AQS使用一个int类型的成员变量表示同步状态,并通过内置的FIFO同步队列来完成资源获取线程的排队等待。AQS提供了一套模板方法,使用者只需关注同步状态的获取和释放,而复杂的线程入队、出队、阻塞和唤醒等操作由AQS处理。AQS支持独占式和共享式访问同步状态,子类通过重写其提供的方法来实现具体的同步逻辑。

AQS 概述

  • AbstractQueuedSynchronizer 来自于 jdk 1.5,位于 juc 包中,简称为 AQS
  • 类如其名,抽象的队列式的同步器,AQS 定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的 ReentrantLock,ReentrantReadWriteLock,CountDownLatch
  • AQS 中,主要有两部分功能:一部分是操作 state 变量,第二部分是实现排队和阻塞机制

AQS 设计思想

对于使用者来讲,我们无需关心获取资源失败,线程排队,线程阻塞,唤醒等一系列复杂的实现,这些都在 AQS 中为我们处理好了。我们只需要负责处理获取,释放锁资源的状态 state的逻辑即可。这是很经典的模板方法设计模式的应用,AQS 为我们定义好顶级逻辑的骨架,并提取出公用的线程入队列,出队列,阻塞,唤醒等一系列复杂逻辑的实现,将部分简单的可由使用者决定的操作逻辑放到 AQS 的子类中去实现即可

AQS 框架内部探究

AQS 框架内部

在这里插入图片描述

  • AbstractQueuedSynchronizer 被设计为一个抽象类,它使用了一个 volatile 来修饰 int 类型的成员变量 state 来表示同步状态,通过内置的 FIFO (先进先出)双向队列来完成资源获取线程的排队等待工作。通常 AQS 的子类通过继承 AQS 并实现它的抽象方法来管理同步状态
  • AQS 自身没有实现任何同步接口,它仅仅是定义了若干同步状态获取和释放的方法,AQS 既可以支持独占式地访问同步状态( 如ReentrantLock),也可以支持共享式地访问同步状态(如 CountDownLatch),这样就可以方便实现不同类型的同步组件

AQS 访问同步状态 state

重写 AQS 指定的方法时,需要使用 AQS 提供的如下 3 个方法来访问或修改同步状态,不同的锁实现都可以直接调用这 3 个方法

// 同步状态变量,或者代表共享资源
private volatile int state;

// 返回同步状态的当前值。此操作具有 volatile 读的内存语义,因此每次获取的都是最新值
protected final int getState() {
   
   
	return state;
}

// 设置同步状态的最新值。此操作具有 volatile 写的内存语义,因此每次写数据都是写回主内存
protected final void setState(int newState) {
   
   
	state = newState;
}

/**
 * 如果当前状态值等于预期值,则以原子方式将同步状态设置为给定的要更新的值
 *  * @param expect 预期值
 * @param update 写入值
 * @return 如果更新成功返回true,失败则返回false
 */
protected final boolean compareAndSetState(int expect, int update) {
   
   
	// 内部调用 unsafe 的方法,该方法是一个 CAS 方法
    // 这个 unsafe 类,实际上是比 AQS 更加底层的底层框架,或可以认为是 AQS 框架的基石
    // CAS 操作在 Java 中的最底层的实现就是 Unsafe 类提供的,它是作为 Java 语言与 Hospot源码(C++)以及底层操作系统沟通的桥梁
	return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

这三个方法 getState()、setState()、compareAndSetState() 都是 final 方法,是 AQS 提供的通用的访问同步状态的方法,能保证线程安全,我们直接调用即可

AQS 的自定义(子类)同步器的实现

  • AQS 定义了两种资源享用方式:Exclusive(独占,如 ReentrantLock)和 Share(共享,如 CountDownLatch
  • 不同的自定义同步器竞争使用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源 state 的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队,唤醒出队等),AQS 已经在顶层实现好了。自定义同步器实现时主要实现以下几种方法
/**
 * 独占式获取锁,该方法需要查询当前状态并判断锁是否符合预期,然后再进行 CAS 设置锁。返回true 则成功,否则失败
 */
protected boolean tryAcquire(int arg) {
   
   
    throw new UnsupportedOperationException();
}

/**
 * 独占式释放锁,等待获取锁的线程将有机会获取锁。返回 true 则成功,否则失败
 */
protected boolean tryRelease(int arg) {
   
   
    throw new UnsupportedOperationException();
}

/**
 * 共享式获取锁,返回大于等于 0 的值表示获取成功,否则失败
 *
 * 如果返回值小于 0,表示当前线程获取共享锁失败
 * 如果返回值大于 0,表示当前线程获取共享锁成功,并且接下来其他线程尝试获取共享锁的行为很可能成功
 * 如果返回值等于 0,表示当前线程共享锁成功,但是接下来其他线程尝试获取共享锁的行为会失败(实际上也有可能成功,在后面的源码部分会将)
 */
protected int tryAcquireShared(int arg) {
   
   
    throw new UnsupportedOperationException();
}

/**
 * 共享式释放锁。返回 true 成功,否则失败
 */
protected boolean tryReleaseShared(int arg) {
   
   
    throw new UnsupportedOperationException();
}

/**
 * 当前 AQS 是否在独占模式下被线程占用,一般表示是否被前当线程独占;
 * 如果同步是以独占方式进行的,则返回 true;其他情况则返回 false
 */
protected boolean isHeldExclusively() {
   
   
    throw new UnsupportedOperationException();
}

AQS 中同步队列

数据结构之队列

  • 队列 queue 是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。队列的工作原理与现实生活中的队列完全相同。类似火车头进入山洞,先进入山洞的车厢就先出来山洞,后进入山洞的火车头后出来山洞。队列的工作原理与此相同
  • 队列是一种先进先出(First In First Out)的线性表,简称 FIFO。允许插入的一端称为队尾,允许删除的一端称为队头。假设队列是 q = (a1,a2,…,an),那么 a1 就是队头元素,而 an 是队尾元素。这样我们就可以删除时,总是从 a1 开始,而插入时,列在最后

在这里插入图片描述
因为队列属于线性表,因此队列也可以采用顺序存储结构和链式存储结构来实现。Java 中已经提供了很多线程的队列的实现,比如 JUC 中的各种阻塞、非阻塞队列

AQS 中同步队列数据结构(双链表实现的队列)

public abstract class AbstractQueuedSynchronizer extends
            AbstractOwnableSynchronizer implements java.io.Serializable {
   
   
		
	protected AbstractQueuedSynchronizer() {
   
    }
	
  	// 队列头结点,实际上是一个哨兵结点,不代表任何线程,head 所指向的 Node 的 thread 属性永远是 null
    private transient volatile Node head;
    
    // 队列尾结点,后续的结点都加入到队列尾部    
    private transient volatile Node tail;
      
    // 同步状态  
    private volatile int state;

    // Node内部类,同步队列的结点类型
    static final class Node {
   
   

        // 共享模式下构造的结点,用来标记该线程是获取共享资源时被阻塞挂起后放入AQS 队列的
    	static final Node SHARED = new Node();
            
        // 独占模式下构造的结点,用来标记该线程是获取独占资源时被阻塞挂起后放入AQS 队列的    
		static final Node EXCLUSIVE = null;

       /**
        * 表示当前结点(线程)需要取消等待
        * 由于在同步队列中等待的线程发生等待超时、中断、异常,即放弃获取锁,需要从同步队列中取消等待,就会变成这个状态
        * 如果结点进入该状态,那么不会再变成其他状态
        */
		static final int CANCELLED = 1;
		
       /**
        * 表示当前结点(线程)的后续结点(线程)需要取消等待(被唤醒)
        * 如果一个结点状态被设置为SIGNAL,那么后继结点的线程处于挂起或者即将挂起的状态
        * 当前结点的线程如果释放了锁或者放弃获取锁并且结点状态为SIGNAL,那么将会尝试唤醒后继结点的线程以运行
        * 这个状态通常是由后继结点给前驱结点设置的。一个结点的线程将被挂起时,会尝试设置前驱结点的状态为SIGNAL
        */
		static final int SIGNAL = -1;
		
       /**
        * 线程在等待队列里面等待,waitStatus值表示线程正在等待条件
        * 原本结点在等待队列中,结点线程等待在Condition上,当其他线程对Condition调用了signal()方法之后
        * 该结点会从从等待队列中转移到同步队列中,进行同步状态的获取
        */
		static final int CONDITION = -2;
		
        // 共享模式下,前继结点不仅会唤醒其后继结点
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值