AQS简介
- 抽象队列同步器AbstractQueuedSynchronizer (简称AQS)是
用来构建锁或者其他同步组件的基础框架
,它使用了一个int成员变量来表示同步状态,通过内置的FIFO(first-in-first-out)同步队列来控制获取共享资源的线程
。 - AbstractQueuedSynchronizer 使用的方法是继承,
子类通过继承同步器并需要实现它的方法来管理状态
,管理的方式是通过类似acquire和release的方式来操纵状态。 - AbstractQueuedSynchronizer 类被设计为大多数同步组件的基类,这些
同步组件都依赖于单个原子值(int)来控制同步状态,子类必须要定义获取同步与释放状态的方法
。
private volatile int state;//共享变量,使用volatile修饰保证线程可见性
- AQS子类应该为
自定义同步组件的静态内部类
,AQS自身没有实现任何同步接口,它仅仅是定义了若干同步状态获取和释放的方法来供自定义同步组件使用
。 - 同步器既可以支持独占式地获取同步状态,也可以支持共享式地获取同步状态。同步器也是实现锁的关键,在锁的实现中聚合同步器,利用同步器实现锁的语义。
- Exclusive独占锁:只有一个线程能执行,根据锁的获取机制,又分为“公平锁”和“非公平锁”。如
ReentrantLock、ReentrantReadWriteLock.WriteLock
。- 公平锁:等待队列中按照FIFO的原则获得锁,等待时间越长的线程越先获得锁。
- 非公平锁:线程获取锁的时候,无需等待队列直接获取锁。
- Share共享:多个线程可同时执行,如
ReentrantReadWriteLock.ReadLock、CyclicBarrier、CountDownLatch、Semaphore
。
- Exclusive独占锁:只有一个线程能执行,根据锁的获取机制,又分为“公平锁”和“非公平锁”。如
AQS原理图
AQS是个双端双向链表。当线程获取资源失败(比如tryAcquire时试图设置state状态失败),会被构造成一个结点加入CLH队列中,同时当前线程会被阻塞在队列中(通过LockSupport.park实现,其实是等待态)。当持有同步状态的线程释放同步状态时,会唤醒后继结点,然后此结点线程继续加入到对同步状态的争夺中。
AQS类的方法
- 在AQS中提供了三种方法
- getState() :获取当前的同步状态
- setState(int newState) :设置当前同步状态
- compareAndSetState(int expect, int update):
使用CAS设置当前状态
,该方法能保证状态设置的原子性。如果当前状态值等于预期值,则以原子方式将同步状态设置为给定的更新值。此操作具有 volatile 读和写的内存语义
。
自定义同步器实现的方法(子类中可以重写的方法)
boolean isHeldExclusively()
:该线程是否正在独占资源。boolean tryAcquire(int)
:独占方式。通过CAS操作设置同步状态,尝试获取资源,成功则返回true,失败则返回false。boolean tryRelease(int)
:独占方式。尝试释放资源,成功则返回true,失败则返回false。int tryAcquireShared(int)
:共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。boolean tryReleaseShared(int)
:共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。
获取同步状态与释放同步状态方法
- 独占式获取和释放同步状态
void acquire(int arg):独占式获取同步状态
,如果当前线程获取同步状态成功,则返回,否则进入同步队列等待,该方法会调用tryAcquire(int arg)方法。void acquireInterruptibly(int arg)
:与 void acquire(int arg)基本逻辑相同,但是该方法响应中断,如果当前没有获取到同步状态,那么就会进入等待队列,如果当前线程被中断(Thread().interrupt()),那么该方法将会抛出InterruptedException并返回boolean tryAcquireNanos(int arg, long nanosTimeout)
:在acquireInterruptibly(int arg)的基础上,增加了超时限制,如果当前线程没有获取到同步状态,那么将返回fase,反之返回true。boolean release(int arg)
:独占式的释放同步状态
- 共享式获取和释放同步状态
void acquireShared(int arg):共享式的获取同步状态
,如果当前线程未获取到同步状态,将会进入同步队列等待,与独占式获取的主要区别是在同一时刻可以有多个线程获取到同步状态。void acquireSharedInterruptibly(int arg)
:在acquireShared(int arg)的基本逻辑相同,增加了响应中断。boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
:在acquireSharedInterruptibly的基础上,增加了超时限制。boolean releaseShared(int arg)
:共享式的释放同步状态
AQS具体实现及内部原理
- FIFO队列
- AQS中主要通过一个FIFO(first-in-first-out)来控制线程的同步。
- 在实际程序中,AQS会将获取同步状态的线程构造成一个Node节点,并将该节点加入到队列中。如果该线程获取同步状态失败会阻塞该线程,当同步状态释放时,会把头节点中的线程唤醒,使其尝试获取同步状态。
- Node节点结构
static final class Node {
static final Node SHARED = new Node();
static final Node EXCLUSIVE = null;
static final int CANCELLED = 1; //被中断或获取同步状态超时的线程将会被置为当前状态,且该状态下的线程不会再阻塞。
static final int SIGNAL = -1; //当前节点的线程如果释放了或取消了同步状态,将会将当前节点的状态标志位SINGAL,用于通知当前节点的下一节点,准备获取同步状态。
static final int CONDITION = -2; //当前节点在Condition中的等待队列上,(关于Condition会在下篇文章进行介绍),其他线程调用了Condition的singal()方法后,该节点会从等待队列转移到AQS的同步队列中,等待获取同步锁。
static final int PROPAGATE = -3; //与共享式获取同步状态有关,该状态标识的节点对应线程处于可运行的状态。
// 0:None of the above,新结点会处于这种状态。
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node nextWaiter;
}
* Node结点类是AQS的一个静态内部类,是等待队列中的结点类。这个等待队列是一个"CLH"锁队列的变体。
* CLH锁即Craig, Landin, and Hagersten (CLH) locks,CLH锁是一个自旋锁,能确保无饥饿性,提供先来先服务的公平性。
AQS同步队列具体实现结构
- 在AQS中的同步队列中,分别有两个指针(你也可以叫做对象的引用),一个head指针指向队列中的头节点,一个tail指针指向队列中的尾节点。
- AQS添加尾节点
- AQS添加头节点