AQS原理解析

1. 什么是AQS


AQS的全称是AbstractQueuedSynchronizer,即抽象队列同步器,这个类在java.uitl.concurrent.locks包下面。

AQS就是一个抽象类,主要用来构建锁和同步器

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

AQS为构建锁和同步器提供了一些通用功能的实现,因此使用AQS能简单且高效地构建出应用广泛的大量的同步器
比如常用的ReentrantLockSemaphore,其他类似ReentrantReadWriteLockSynchronousQueue等等皆是基于 AQS 的

2. AQS原理


2.1 AQS核心思想


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

CLH锁是对自旋锁的一种改进,是一个虚拟的双向队列(虚拟的双向队列不存在队列实例,仅存在结点之间的关系),暂时获取不到锁的线程将被加入到队列中。AQS将每条请求共享资源的线程封装为一个CLH队列锁的一个结点(Node)来实现锁的分配。在CLH队列锁中,一个节点表示一个线程,它保存着线程的引用、当前节点在队列中状态、前驱节点、后继节点。

Node结点的源码如下所示:

abstract static class Node {  
    volatile Node prev;       // 前驱结点
    volatile Node next;       // 后继结点 
    Thread waiter;            // 线程引用
    volatile int status;      // 线程的状态
  
    // methods for atomic operations    final boolean casPrev(Node c, Node v) {  // for cleanQueue  
        return U.weakCompareAndSetReference(this, PREV, c, v);  
    }  
    final boolean casNext(Node c, Node v) {  // for cleanQueue  
        return U.weakCompareAndSetReference(this, NEXT, c, v);  
    }  
    final int getAndUnsetStatus(int v) {     // for signalling  
        return U.getAndBitwiseAndInt(this, STATUS, ~v);  
    }  
    final void setPrevRelaxed(Node p) {      // for off-queue assignment  
        U.putReference(this, PREV, p);  
    }  
    final void setStatusRelaxed(int s) {     // for off-queue assignment  
        U.putInt(this, STATUS, s);  
    }  
    final void clearStatus() {               // for reducing unneeded signals  
        U.putIntOpaque(this, STATUS, 0);  
    }  
  
    private static final long STATUS  = U.objectFieldOffset(Node.class, "status");  
    private static final long NEXT    = U.objectFieldOffset(Node.class, "next");  
    private static final long PREV    = U.objectFieldOffset(Node.class, "prev");  
}

CLH队列结构如下图所示:
![[Pasted image 20240819145542.png]]

AQS(AbstractQueuedSynchronizer)的核心原理图:
![[Pasted image 20240819145559.png]]

AQS使用成员变量state,表示同步状态,通过内置的FIFO线程等待/等待队列来完成获取资源线程的排队工作。
AQS抽象类中内置了head和tail结点。

/**  
 * Head of the wait queue, lazily initialized. */
 private transient volatile Node head;  
  
/**  
 * Tail of the wait queue. After initialization, modified only via casTail. */
 private transient volatile Node tail;

state变量由volatile修饰,用于展示当前临界资源的获取锁的情况。

private volatile int state;

另外,状态信息state可以通过protected类型的getState()setState()compareAndSetState()进行操作。并且,这几个方法都是通过final修饰的,在子类无法被重写。

//返回同步状态的当前值
protected final int getState() {
     return state;
}
 // 设置同步状态的值
protected final void setState(int newState) {
     state = newState;
}
//原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值)
protected final boolean compareAndSetState(int expect, int update) {
      return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

以可重入的互斥锁ReentrantLock为例,它的内部维护了一个state变量,用来表示锁的占用状态

  1. state的初始值为0,表示锁处于未锁定状态
  2. 当线程A调用了lock()方法时,会尝试通过tryAcquire()方法独占锁并让state的值+1。
  3. 如果成功了,那么线程A就获取到了锁,如果失败了,那么线程A就会被加入到一个等待队列中(CLH队列)直到其他线程释放该锁。
  4. 假设线程A获取锁成功了,释放锁之前,A线程自己是可以重复获取此锁的(state会累加)。
  5. 这是可重入的表现:一个线程可以多次获取同一个锁而不会被阻塞,但是,这也意味着,一个线程必须释放全部次数的锁,
    才可以让state变为0,就是让锁恢复到未锁定的状态。只有这样,其他等待的线程才可以去获取锁。

线程 A 尝试获取锁的过程如下图所示:
![[Pasted image 20240819151108.png]]

倒计时器CountDownLatch为例子:

  1. 任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。
  2. 这N个子线程开始执行任务,每执行完一个子线程,就调用一次countDown()方法。
  3. 该方法会尝试使用CAS操作,让state的值减少1。
  4. 当所有的子线程执行完毕后,CountDownLatch会调用unpark()方法,唤醒主线程。
  5. 这时主线程就可以从await()方法(CountDownLatch中的await()方法而非AQS中的)返回,继续执行后续的操作。

2.2 AQS资源共享方式


AQS定义两种资源共享方式:Exclusive(独占,只有一个线程能执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore和CountDownLatch)

一般来说,自定义同步器的共享方式要么是独占,要么是共享,他们只需要实现tryAcquire-tryReleasetryAcquireShared-tryReleaseShared中的一种即可。但 AQS 也支持自定义同步器同时实现独占和共享两种方式,如ReentrantReadWriteLock

2.3 自定义同步器


同步器的设计是基于模板方法模式的,如果需要自定义同步器,一般的方式如下:

  1. 使用者继承AbstractQueuedSynchronizer 并重写指定的方法。
  2. 将 AQS 组合在自定义同步组件的实现中,并调用其模板方法,而这些模板方法会调用使用者重写的方法。

AQS使用了模板方法模式,自定义同步器时需要重写下面的几个AQS提供的钩子方法:

//独占方式。尝试获取资源,成功则返回true,失败则返回false。
protected boolean tryAcquire(int)
//独占方式。尝试释放资源,成功则返回true,失败则返回false。
protected boolean tryRelease(int)
//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
protected int tryAcquireShared(int)
//共享方式。尝试释放资源,成功则返回true,失败则返回false。
protected boolean tryReleaseShared(int)
//该线程是否正在独占资源。只有用到condition才需要去实现它。
protected boolean isHeldExclusively()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值