AQS概念
AQS(AbstractQuenedSynchronizer)抽象队列同步器,是⼀种锁机制
。 在java.util .concurrent⼯具包中,这个包简称JUC。
AQS底层实现原理
AQS维护了⼀个 volatile 修饰的共享变量 state 和⼀个 FIFO 线程等待队列,多线程争⽤资源被阻塞的时候就会进⼊这个队列。state 是共享变量,其访问⽅式有如下三种:getState()、setState()、compareAndSetState()
,通过尝试获取共享变量 state 的结果来对线程的状态作出处理。
AQS 是将每⼀条请求共享资源的线程封装成⼀个 CLH 队列(虚拟的双向队列)的⼀个结点来实现锁的分配。根据 volatile 修饰的 state 共享变量,线程通过 CAS(Compare and swap) 去改变状态。如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制 AQS 是基于 CLH 锁 (Craig, Landin, and Hagersten locks) 实现的。
这也是网上的一种说法,参考一下:
AQS是.NET Framework中提供的一种同步机制,意为"Advanced Synchronization (高级同步)",
可以用来控制多线程操作中各个线程之间的访问顺序和优先级。 AQS的基本思想是,它提供一种资源管理的方式,
可以让多个线程安全地共享同一个资源,通过AQS提供的同步机制,可以让线程按照一定的次序和优先级进入和离开共享资源,
从而保证线程操作的正确性和有序性。主要包括信号量(Semaphore)、倒计时门闩(CountdownEvent)、读写锁(ReaderWriterLock)等同步类。
除此之外,AQS还提供了自定义同步机制的接口IWaitableTimer、IAsyncWaitHandle等。
AQS是Java中也有的机制,称作AbstractQueuedSynchronizer。
原理总结:简单来说 AQS 就是一种同步机制,可以让多个线程安全地共享同一个资源,AQS 是基于 CLH 队列锁和 被 volatile 修饰的共享变量 state 实现的,线程通过 CAS 操作改变状态符状态,成功则获取锁,失败则被加入等待队列等待被唤醒
AQS(AbstractQueuedSynchronizer)的核心原理图如下:
AQS 使用 int 成员变量 state 表示同步状态,通过内置的 线程等待队列 来完成获取资源线程的排队工作。state 变量由 volatile 修饰,用于展示当前临界资源的获锁情况。// 共享变量,使用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 初始值为 0,表示未锁定状态。A 线程 lock() 时,会调用 tryAcquire() 独占该锁并将 state+1 。此后,其他线程再 tryAcquire() 时就会失败,直到 A 线程 unlock() 到 state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A 线程自己是可以重复获取此锁的(state 会累加),这就是可重入的概念。但要注意,获取多少次就要释放多少次,这样才能保证 state 是能回到零态的。
再以 CountDownLatch 以例,任务分为 N 个子线程去执行,state 也初始化为 N(注意 N 要与线程个数一致)。这 N 个子线程是并行执行的,每个子线程执行完后countDown() 一次,state 会 CAS(Compare and Swap) 减 1。等到所有子线程都执行完后 (即 state=0 ),会 unpark() 主调用线程,然后主调用线程就会从 await() 函数返回,继续后余动作。
CLH队列锁
CLH 锁是对自旋锁的一种改进,是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系),暂时获取不到锁的线程将被加入到该队列中。AQS 将每条请求共享资源的线程封装成一个 CLH 队列锁的一个结点(Node)来实现锁的分配。在 CLH 队列锁中,一个节点表示一个线程,它保存着线程的引用(thread)、 当前节点在队列中的状态(waitStatus)、前驱节点(prev)、后继节点(next)。CLH 队列锁结构如下图所示:
如果想要详细了解 CLH 锁,可以参考这篇文章——Java AQS 核心数据结构-CLH 锁
AQS 资源共享方式
AQS 定义两种资源共享方式:Exclusive(独占,只有一个线程能执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)。一般来说,自定义同步器的共享方式要么是独占,要么是共享,他们也只需实现tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一种即可。但 AQS 也支持自定义同步器同时实现独占和共享两种方式,如ReentrantReadWriteLock。