AQS的架构思想,及代码浅析
A其实本来这篇文章一直纠结要不要写,想写又觉得自己水平不够,但是不写的话,后面的ReentrantLock又是基于AQS的,可以说是整个Java并发编程中JUC包中最核心的部分,所以还是打算写一下,水平有限,本篇文章只是对AQS比较浅的探讨,不会太过深入源码,更多的是去理解AQS的整个过程
什么是AQS
AQS的全称是AbstractQueuedSynchronizer,译名抽象队列同步器。可以说是整个Java并发编程包JUC的底层实现,AQS是一种采用CAS+自旋的方式,然后采用一个双向链表组成的队列,结合一个state来实现的同步器。很多JUC包,比如ReentrantLock、Semaphore、CountDownLatch、CyclicBarrier等并发类均是继承AQS,实现AQS的模板方法,来实现的。
框架和整体思想
用个人的理解来解读一下这个图,AQS的核心就是一个标记为volatile的state状态值+一个队列,,这个锁是否被抢占到则是用这个state来标记的,因此把state设置为volatile的目的也是保证对所有的线程可见,我们假设有三个线程去抢占锁,此时线程1抢占成功了,就会把state值设置为1,然后其他的没有抢到的线程则会被封装成一个Node进入到这个阻塞队列中。等待持有锁的线程释放之后,会唤醒离头结点最近的那个节点对应的线程。继续尝试抢占state。由这个过程分析,其实整个AQS都是基于CAS,而CAS又是基于UnSafe类中的一些方法,来保证同时只有一个线程来修改state。
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
上图大概的说明了一下AQS的整个流程。总结一下就是:对于AQS来说,加锁就是对state的状态值进行+1 如果是可重入的锁就反复加1,释放锁则是对state状态值-1.当state为0的时候,代表没有被其他线程持有,大于0的时候代表已经被其他线程持有了,需要进入到阻塞队列中等待释放锁后的唤醒。
代码层面
AQS(AbstractQueuedSynchronizer)是Java并发工具的基础,采用乐观锁,通过CAS与自旋轻量级的获取锁。
下面简单分析一下他的核心实现:
- 核心属性
/**
* The synchronization state.
* 同步状态,0代表锁没有被占用,大于0代表锁被占用且表示锁被重入的次数
*/
private volatile int state;
/**
*存储当前获取锁的线程,继承自AbstractOwnableSynchronizer
*/
private transient Thread exclusiveOwnerThread;
- 队列实现
/**
* 对首,只是一个标志位,不存储线程
*/
private transient volatile Node head;
/**
* 队尾,新增加的等待线程入队节点
*/
private transient volatile Node tail;
- Node节点
/**
*当前节点所代表的线程
*/
volatile Thread thread;
// 双向链表,每个节点需要保存自己的前驱节点和后继节点的引用
volatile Node prev;
volatile Node next;
// 线程所处的等待锁的状态,初始化时,该值为0
volatile int waitStatus;
static final int CANCELLED = 1;
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
/**
* 用于条件队列与共享锁
*/
Node nextWaiter;
-
队列
AQS中的队列是双向链表,依赖于Node节点中的prev和next属性,队列中存储了等待获取锁的集合。队列中有head和tail节点,头节点不存储等待锁线程。
对于AQS获取锁和释放锁这一块,我结合ReentrantLock来说明。本篇只谈一下AQS的基本架构和思想
获取锁
我们一般都是这么使用ReentrantLock获取锁的:
//非公平锁
ReentrantLock lock = new ReentrantLock();
lock.lock();
lock方法:
public void lock() {
sync.lock();
}
Sync为ReentrantLock里面的一个内部类,它继承AQS(AbstractQueuedSynchronizer),它有两个子类:公平锁FairSync和非公平锁NonfairSync。 ReentrantLock里面大部分的功能都是委托给Sync来实现的,同时Sync内部定义了lock()抽象方法由其子类去实现,默认实现了nonfairTryAcquire(int acquires)方法,可以看出它是非公平锁的默认实现方式。下面我们看非公平锁的lock()方法:
final void lock() {
//尝试获取锁
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
//获取失败,调用AQS的acquire(int arg)方法
acquire(1);
}
首先会第一次尝试快速获取锁,如果获取失败,则调用acquire(int arg)方法,该方法定义在AQS中,如下:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
- tryAcquire(int arg)
由子类实现,获取锁。 - addWaiter(Node mode)
获取锁失败后,将等待线程封装成Node加入等待队列,由AQS实现。 - acquireQueued(final Node node, int arg)
在队列中如果其前驱节点是头节点,就循环获取锁,获取锁成功就返回。
如果其前驱不是头节点,或者是头节点但是获取锁失败,挂起当前线程。由AQS实现。 - selfInterrupt()
自我中断,当获取锁的时候,发生中断时记录下来,推迟到抢锁结束后中断线程
这个方法首先调用tryAcquire(int arg)方法,在AQS中讲述过,tryAcquire(int arg)需要自定义同步组件提供实现,非公平锁实现如下:
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
//当前线程
final Thread current = Thread.currentThread();
//获取同步状态
int c = getState();
//state == 0,表示没有该锁处于空闲状态
if (c == 0) {
//获取锁成功,设置为当前线程所有
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//线程重入
//判断锁持有的线程是否为当前线程
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
该方法主要逻辑:首先判断同步状态state == 0 ?,如果是表示该锁还没有被线程持有,直接通过CAS获取同步状态,如果成功返回true。如果state != 0,则判断当前线程是否为获取锁的线程,如果是则获取锁,成功返回true。成功获取锁的线程再次获取锁,这是增加了同步状态state。
释放锁
获取同步锁后,使用完毕则需要释放锁,ReentrantLock提供了unlock释放锁:
public void unlock() {
sync.release(1);
}
unlock内部使用Sync的release(int arg)释放锁,release(int arg)是在AQS中定义的:
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
与获取同步状态的acquire(int arg)方法相似,释放同步状态的tryRelease(int arg)同样是需要自定义同步组件自己实现:
protected final boolean tryRelease(int releases) {
//减掉releases
int c = getState() - releases;
//如果释放的不是持有锁的线程,抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//state == 0 表示已经释放完全了,其他线程可以获取同步状态了
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
只有当同步状态彻底释放后该方法才会返回true。当state == 0 时,则将锁持有线程设置为null,free= true,表示释放成功。