AQS原理初探
AQS全称为AbstractQueuedSynchronizer,如果直接按名字翻译的话就是抽象队列式同步器,AQS定义了一套多线程访问共享资源的同步器框架,许多同步类的实现都依赖于它,如ReentrantLock(可重入锁)、Semaphore(信号量)等等。它是构建锁或者其他同步组件的基础框架。
可重入锁
这里解释一下可重入锁:可重入锁就是如果某个线程已经获得某个锁,可以再次获取该锁而不会导致死锁。ReentrantLock以及synchronized都是可重入锁,其中ReentrantLock需要自己手动释放(如果获取次数和释放次数不一致会有问题),而synchronized会自动释放。
AQS成员变量
private transient volatile Node head; // 队列的头节点
private transient volatile Node tail; // 队列的尾节点
private volatile int state; // 标识同步状态
使用int类型的state是AQS定义线程获取锁的模式包括独占模式和共享模式,所以使用int可以标识这两种状态。
AQS如何实现同步
AQS使用一个int类型的成员变量state来表示同步状态,当state>0时表示已经获得了锁,当state=0时表示释放了锁,它提供了三个方法getState(),setState(int newState),compareAndSetState(int expect, int update)来对同步状态state进行操作,当然AQS可以确保对state的操作是安全的。
state是volatile类型的变量,volatie保证了共享变量的可见性和有序性,但是不保证原子性,所以这里还有一个cas操作保证了设置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) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
AQS定义了两种资源共享方式:Exclusive(独占方式,只有一个线程执行)和Share(共享式,多个线程可以同时执行)
CLH同步队列
CLH同步队列是一个FIFO双向队列,AQS依赖它来完成同步状态的管理,当前线程如果获取同步状态失败时,AQS则会将当前线程已经等待状态等信息构造成一个节点(Node)并将其加入到CLH同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点唤醒(公平锁),使其再次尝试获取同步状态。
在CLH同步队列中,一个节点表示一个线程,它保存着线程的引用(thread)、状态(waitStatus)、前驱节点(prev)、后继节点(next),其数据结构如下
队列的node节点包含如下字段:
/** 标识该节点在共享模式 */
static final Node SHARED = new Node();
/** 标识该节点在独占模式 */
static final Node EXCLUSIVE = null;
/** 标识此线程取消了争夺锁的操作 */
static final int CANCELLED = 1;
// 表示当前节点的后继节点对应的线程需要被唤醒
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3;
// 当前线程的状态
volatile int waitStatus;
/**
* 当前结点的前置结点
*/
volatile Node prev;
/**
* 当前结点的下一个结点
*/
volatile Node next;
/**
* 使该结点排队的线程,在构造函数中初始化,并且用完以后置为null
*/
volatile Thread thread;
/**
* 指向下一个等待某一条件的结点,或者处于SHARED状态的结点
*/
Node nextWaiter;
如何自定义同步器
不同的自定义的同步器争用共享资源的方式不同,自定义同步器在实现时只需要实现共享资源的state的获取于释放方式即可,至于具体线程等待队列的维护AQS顶层已经为我们实现了,自定义同步器需要实现以下方法:
- isHeldExclusively():该线程是否正在独占资源
- tryAcquire(int):以独占的方式尝试获取资源,成功则返回true,失败则返回false,成功获取同步状态以后,其他线程需要等待该线程释放同步状态才能获取同步状态
- tryRelease(int):以独占的方式去尝试释放资源,成功则返回true,失败返回false
- tryAcquireShared(int):