AQS出于“分离变与不变”的原则,基于模板模式实现。AQS为锁获取、锁释放的排队和出队过程提供了一系列的模板方法。由于JUC的显式锁种类丰富,因此AQS将不同锁的具体操作抽取为钩子方法,供各种锁的子类(或者其内部类)去实现。
AQS 中维持了一个单一的volatile修饰的状态信息state,AQS使用int类型的state 标示锁的状态,可以理解为锁的同步状态。
由于setState()无法保证原子性,因此AQS给我们提供了compareAndSetState()方法利用底层 UnSafe的 CAS 机制来实现原子性。compareAndSetState()方法实际上调用的是unsafe成员的 compareAndSwapInt()方法。
以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程执行该锁的lock()操作时,会调用tryAcquire()独占该锁并将state加1。此后,其他线程再tryAcquire()时就会失败,直到A线程 unlock()到state=0(释放锁)为止,其他线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state能回到零态。
AbstractQueuedSynchronizer继承了 AbstractOwnableSynchronizer,这个基类只有一个变量叫 exclusiveOwnerThread,表示当前占用该锁的线程,并且提供了相应的get()和set()方法,具体如下:
public abstract class AbstractOwnableSynchronizer
implements java.io.Serializable {
//表示当前占用该锁的线程
private transient Thread exclusiveOwnerThread;
// 省略 get/set 方法
}
AQS是一个虚拟队列,不存在队列实例,仅存在节点之间的前后关系。节点类型通过内部类 Node 定义,其核心的成员如下:
static final class Node {
/**节点等待状态值1:取消状态*/
static final int CANCELLED = 1;
/**节点等待状态值-I:标识后继线程处于等待状态*/
static final int SIGNAL -1:
/**节点等待状态值-2:标识当前线程正在进行条件等待*/
static final int CONDITION--2;
/**节点等待状态值-3:标识下一次共享锁的acquireShared操作需要无条件传播*/
static final int PROPAGATE --3;
//节点状态:值为SIGNAL、CANCELLED、CONDITION、PROPAGATE、0
//普通的同步节点的初始值为0,条件等待节点的初始值为CONDITION(-2)
volatile int waitstatus;
//节点所对应的线程,为抢锁线程或者条件等待线程
volatile Thread thread;
//前驱节点,当前节点会在前驱节点上自旋,循环检查前驱节点的waitstatus 状态
volatile Node prevs
//后继节点
volatile Node next;
//此属性指向下一个条件等待节点,即其条件队列上的后继节点
//若当前Node不是普通节点而是条件等待节点,则节点处于某个条件的等待队列上
Node nextWaiter;
......
}
1. waitStatus 属性
每个节点与等待线程关联,每个节点维护一个状态waitStatus,waitStatus的各种值以常量形式进行定义。waitStatus的各常量值具体如下:
(1) static final int CANCELLED =1
waitStatus值为1时表示该线程节点已释放(超时、中断),已取消的节点不会再阻塞,表示线程因为中断或者等待超时,需要从等待队列中取消等待。
由于该节点线程等待超时或者被中断,需要从同步队列中取消等待,因此该线程被置1.节点进入了取消状态,该类型节点不会参与竞争,且会一直保持取消状态。
(2) static final int SIGNAL=-1
waitStatus为SIGNAL(-1)时表示其后继的节点处于等待状态,当前节点对应的线程如果释放了同步状态或者被取消,就会通知后继节点,使后继节点的线程得以运行。
(3) static final int CONDITION=-2
waitStatus为-2时,表示该线程在条件队列中阻塞(Condition有使用),表示节点在等待队列中(这里指的是等待在某个锁的CONDITION上,关于CONDITION的原理后面会讲到),当持有锁的线程调用了CONDITION的signal()方法之后,节点会从该CONDITION的等待队列转移到该锁的同步队列上,去竞争锁(注意:这里的同步队列就是我们讲的AQS维护的 FIFO队列,等待队列则是每个 CONDITION关联的队列)。
节点处于等待队列中,节点线程等待在CONDITION上,当其他线程对CONDITION调用了 signal)方法后,该节点从等待队列中转移到同步队列中,加入对同步状态的获取中。
(4) static final int PROPAGATE=-3
waitStatus为一3时,表示下一个线程获取共享锁后,自己的共享状态会被无条件地传播下去,因为共享锁可能出现同时有N个锁可以用,这时直接让后面的N个节点都来工作。这种状态在CountDownLatch中使用到了。
为什么当一个节点的线程获取共享锁后,要唤醒后继共享节点?共享锁是可以多个线程共有
获取锁。当一个节点的线程获取共享锁后,必然要通知后继共享节点的线程也可以获取锁了,这样就不会让其他等待的线程等很久,这种向后通知(传播)的目的也是尽快通知其他等待的线程尽快获取锁。
(5) waitStatus为0
waiStatus为0时,表示当前节点处于初始状态。 Node节点的waitStatus状态为以上5种状态的一种。
2.thread 成员
Node的thread成员用来存放进入AQS队列中的线程引用: Node的nextWaiter成员用来指向自己的后继等待节点,此成员只有线程处于条件等待队列中的时候使用。
3.抢占类型常量标识
Node 节点还定义了两个抢占类型常量标识:SHARED和EXCLUSIVE,具体如下:
static final class Node {
//标识节点在抢占共享锁
static final Node SHARED =new Node();
//标识节点在抢占独占锁
static final Node EXCLUSIVE = null;
......
}
SHARED表示线程是因为获取共享资源时阻塞而被添加到队列中的;EXCLUSIVE 表示线程是因为获取独占资源时阻塞而被添加到队列中的。
AQS 的内部队列是CLH队列的变种,每当线程通过AQS获取锁失败时,线程将被封装成一个Node节点,通过CAS原子操作插入队列尾部。当有线程释放锁时,AQS会尝试让队头的后继节点占用锁。
AQS通过内置的 FIFO双向队列来完成线程的排队工作,内部通过节点head和tail记录队首和队尾元素,元素的节点类型为Node类型,具体如下:
/*首节点的引用*/
private transient volatile Node head;
/*尾节点的引用*/
private transient volatile Node tail;
AQS的首节点和尾节点都是懒加载的。懒加载的意思是在需要的时候才真正创建。只有在线程竞争失败的情况下,有新线程加入同步队列时,AQS才创建一个head 节点。head 节点只能被 setHead()方法修改,并且节点的waitStatus不能为CANCELLED。尾节点只在有新线程阻塞时才被创建。
一个包含4个节点的AQS同步队列的基本结构如图所示。
AQS是java.util.concurrent包的一个同步器,它实现了锁的基本抽象功能,支持独占锁与共享锁两种方式。该类是使用模板模式来实现的,成为构建锁和同步器的框架,使用该类可以简单且高效地构造出应用广泛的同步器(或者等待队列)。
java.util.concurrent.locks 包中的显式锁如ReentrantLock、ReentrantReadWriteLock,线程同步工具如Semaphore,异步回调工具如FutureTask等,内部都使用了AQS作为等待队列。通过开发工具进行 AQS的子类导航会发现大量的AQS子类以内部类的形式使用,具体如图所示。
同样,我们也能继承AQS类去实现自己需求的同步器(或锁)。
这里以 ReentrantLock为例为大家介绍一下JUC显式锁与AQS的组合关系。
1.ReentrantLock 与AQS的组合关系
ReentrantLock是一个可重入的互斥锁,又称为“可重入独占锁”。ReentrantLock锁在同一个时间点只能被一个线程锁持有,而可重入的意思是,ReentrantLock锁可以被单个线程多次获取。经过观察,ReentrantLock把所有 Lock接口的操作都委派到一个Sync类上,该类继承了 AbstractQucuedSynchronizer:
static abstract class Sync extends AbstractQueuedSynchronizer {...}
ReentrantLock为了支持公平锁和非公平锁两种模式,为Sync又定义了两个子类,具体如下:
final static class NonfairSync extends Sync {...}
final static class FairSync extends Sync {...}
NonfairSync为非公平(或者不公平)同步器,FairSync为公平同步器。ReentrantLock 提供了两个构造器,具体如下:
public ReentrantLock() { //默认的构造器
sync = new NonfairSync(); //内部使用非公平同步器
}
public ReentrantLock (boolean fair) { //true 为公平锁,否则为非公平锁
sync = fair ? new FairSync() : new NonfairSync();
}
ReentrantLock的默认构造器(无参数构造器)被初始化为一个NonfairSync对象,也就是使用非公平同步器。所以,默认情况下ReentrantLock为非公平锁。带参数的构造器可以根据 fair参数的值具体指定ReentrantLock的内部同步器使用FairSync还是NonfairSyne。
由 ReentrantLock的lock(和unlockO的源码可以看到,它们只是分别调用了sync对象的lockO)和 release()方法。
public void lock(){ //抢占显式锁
sync.lock();
public void unlock () {//释放显式锁
sync,release(1);
}
通过以上委托代码可以看出,ReentrantLock的显式锁操作是委托(或委派)给一个Sync 内部类的实例来完成的。而Sync内部类只是AQS的一个子类,所以本质上ReentrantLock 的显式锁操作是委托(或委派)给AQS完成的。一个ReentrantLock对象的内部一定有一个AQS类型的组合实例,二者之间是组合关系。
ReentrantLock 的内部结构如图所示。
2.显式锁与 AQS之间的组合关系
组合和聚合比较类似,二者都表示整体和部分之间的关系。
聚合关系的特点是,整体由部分构成,但是整体和部分之间并不是强依赖的关系,而是弱依赖的关系,也就是说,即使整体不存在了,部分仍然存在。例如,一个部门由多个员工组成,如果部门撤销了,人员不会消失,人员依然存在。
组合关系的特点是,整体由部分构成,但是整体和部分之间是强依赖的关系,如果整体不存在了,部分也随之消失。例如,一个公司由多个部门组成,如果公司不存在了,部门也将不存在。
可以说,组合关系是一种强依赖的、特殊的聚合关系。
在UML图中,聚合关系用一条带空心菱形箭头的直线表示,组合关系用一条带实心菱形箭头的直线表示。聚合与组合在UML图上的区别如图所示。
由于显式锁与AQS之间是一种强依赖的聚合关系,如果显式锁的实例销毁,其聚合的AQS子类实例也会被销毁,因此显式锁与AQS之间是组合关系。