前言
AQS全名:AbstractQueuedSynchronizer,是在jdk5以后添加的一个类,主要用于实现java的各种锁和同步器,功能强大,后面会分开几章来详细介绍。
AOS
AbstractQueuedSynchronizer有一个父类:AbstractOwnableSynchronizer,这里简称AOS,AOS本身实现非常简单,只有一个临时变量和相应的get/set方法。让线程以独占的方式来持有资源,为创建锁和相关同步器提供了实现基础。
/**
* 独占模式下,持有资源的线程
*/
private transient Thread exclusiveOwnerThread;
/**
* 设置独占线程
*/
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
/**
* 返回独占线程
*/
protected final Thread getExclusiveOwnerThread() {
return exclusiveOwnerThread;
}
transient修饰的变量,不用参与到序列化逻辑,至于为什么要用transient,这里我也不清楚。
the transient keyword is used during serialization. Fields that are marked as transient can not be part of the serialization and deserialization.
Condition
condition接口只在AQS里有实现类:ConditionObject,是专门为锁设定的一个辅助类。接口里的方法就不一一列举了,只有三类,await,signal,signalAll。看着有点面熟吧,其实是模拟Object里的await,notify,notifyAll功能来实现的一套方法。
- await: 释放当前线程持有的锁,并进入等待;
- signal:唤醒一个线程;
- signalAll:唤醒所有的线程;
Node
这里的Node特指AQS里的Node内部类,是实现双向同步队列的基本元素,其中有一个关键变量【waitStatus】,是用volatile修饰的,有以下几个状态:
- 1:CANCELLED,表示线程已经因为超时或者中断而取消等待;
- -1:SIGNAL,表示线程等待被唤醒;
- -2:CONDITION,表示线程处于等待状态;
- -3:PROPAGATE(扩散),在释放共享锁时,应该扩散给其他节点,只需要给头结点设置此状态,以确保传播可以持续。下一个线程来申请共享锁时,可以直接成功获取;
在AQS类中,只保留了head和tail两个node变量,其内部是通过隐形的链表相连,下面补一个示意图:
因为Node类比较简单,只有不到60行,这里贴一下源码,并加上我自己的注解
static final class Node {
// 标记一个节点以共享的模式进入等待
static final Node SHARED = new Node();
// 标记一个节点以独占的模式进入等待
static final Node EXCLUSIVE = null;
// 等待状态,CANCELLED表示线程已经因为超时或者中断而取消等待
static final int CANCELLED = 1;
// 等待状态,SIGNAL表示线程等待被唤醒
static final int SIGNAL = -1;
// 等待状态,CONDITION表示线程处于等待状态
static final int CONDITION = -2;
// 等待状态,PROPAGATE表示下一个请求锁的线程应该直接成功持有
static final int PROPAGATE = -3;
// 初始化为0,表示一个普通节点;如果小于零,则表示有状况,需要特殊处理;如果大于零,则应该忽略;
volatile int waitStatus;
// 当前节点的前一个节点,出队时置空,方便GC
volatile Node prev;
// 当前节点的后一个节点,出队时置空,方便GC;
// 如果next字段为空,并不代表当前节点位于末尾,因为入队操作【enq】要到CAS操作成功之后,才会分配prev
// 如果碰到next为空,可以用tail节点往上检查,做一下双重校验
volatile Node next;
// 将该节点加入队列的线程,在构造时初始化并在使用后置空
volatile Thread thread;
// 获取下一个需要唤醒的节点,wait节点单独用一个链表保存,被唤醒之后,再放到上面的双向链表
Node nextWaiter;
// 判断是否共享锁
final boolean isShared() {
return nextWaiter == SHARED;
}
// 返回当前节点的前驱节点,如果没有,则返回空异常
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
// 用于建立初始头部或共享标记
Node() { // Used to establish initial head or SHARED marker
}
// 新增普通节点,waitStatus = 0
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
// 新增条件等待节点,waitStatus = -2【CONDITION】
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
初始化和CAS
初始化
在AQS类的最下方,有一个静态代码块,其中设置了几个重要属性的初始偏移量,state,head,tail,waitStatus,next。
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long stateOffset;
private static final long headOffset;
private static final long tailOffset;
private static final long waitStatusOffset;
private static final long nextOffset;
static {
try {
stateOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("state"));
headOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("head"));
tailOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
waitStatusOffset = unsafe.objectFieldOffset
(Node.class.getDeclaredField("waitStatus"));
nextOffset = unsafe.objectFieldOffset
(Node.class.getDeclaredField("next"));
} catch (Exception ex) { throw new Error(ex); }
}
偏移量的用途
这里我们挑选一个变量的偏移量来举例说明,毕竟用法都是类似的。看看这个源码的注释,和compareAndSetHead一样,仅仅在入队方法中使用,只能在此修改head和tail变量的地址。
/**
* CAS tail field. Used only by enq.
*/
private final boolean compareAndSetTail(Node expect, Node update) {
return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}
在尾结点的更新代码中,compareAndSwapObject方法引入了此偏移量,这是一个原生方法,底层用C++来实现,大概原理如下:
- 通过传入的变量在对象中的偏移量来获取到变量的地址(对象首地址 + 变量在对象中的偏移量)
- 调用 CompareAndSwap 方法比较变量是否与我们期望的相等,如果相等则更新内存地址的值;
注意:在这里做一个简单的说明,比较和替换的其实是变量的内存地址,而不是变量本身。
其他设置
在AQS中,还有一些其他变量设定。
spinForTimeoutThreshold
尝试获取锁时,先以轮询的方式来重试,如果获取锁则返回,失败则进入等待队列;这样做的好处是可以提高响应能力。默认是1000纳秒,也就是百万分之一秒。