JDK源码那些事儿之传说中的AQS-概览
从这篇文章开始分析JDK源码中AQS的底层实现原理,作为多线程访问共享资源的同步器框架基础实现,涉及到的东西还是比较多的,一起来看看传说中的AQS实现吧
前言
JDK版本号:1.8.0_171
由于涉及到AQS篇幅过多,本篇先总体介绍AQS,分析部分源码实现,对于共享资源的获取和释放以及Condition的源码实现将在之后的文章中通过具体示例进行讲解说明
AQS即AbstractQueuedSynchronizer,直译抽象队列同步器,AQS定义了一套多线程访问共享资源的同步器框架,许多锁和同步类实现都依赖于它,比如常用的ReentrantLock/Semaphore/CountDownLatch等
在深入源码实现之前希望读者可以多看几遍AbstractQueuedSynchronizer的类注释部分,作者在注释部分讲解了很多知识点,可以帮助我们更好的理解AbstractQueuedSynchronizer
AbstractQueuedSynchronizer内部维护了一个volatile int state(代表共享资源)和一个FIFO线程队列(CLH队列变体,多线程争用资源被阻塞时会进入此队列),也就是说多线程访问共享资源时是通过队列来完成后续资源占用或者释放的处理的,队列节点分为EXCLUSIVE(独占)和SHARED(共享)两种模式
通过volatile能够保证多线程下的共享资源的可见性,以互斥锁为例当state=1则表示当前对象锁已经被占有,其他线程来加锁时则会失败,加锁失败的线程会被放入一个FIFO的同步队列中,通过park()操作挂起,等待其他获取锁的线程释放锁才能够被唤醒,此外state的操作都是通过CAS来保证其并发修改的安全性
同时,需要关注下Condition,它是一个多线程间协调通信的工具类,使得某个或者某些线程一起等待某个条件(Condition),只有当该条件具备(signal或者signalAll方法被调用)时,这些等待线程才会被唤醒,从而重新争夺锁,因为它的存在使得开发者对多线程阻塞唤醒机制有了更精细的控制。以ReentrantLock和原有方式对比如下:
原有方式 | AQS方式 |
---|---|
synchronized | ReentrantLock |
Object.wait | Condition.await |
Object.notify | Condition.signal |
数据结构
AbstractQueuedSynchronizer底层是通过FIFO线程队列来完成共享资源的处理的,那么就有必要先去了解下其底层队列的数据结构实现,这里先进行简单说明,一定要注意,在AQS中有两种队列:
- sync queue : 同步队列,属于AbstractQueuedSynchronizer这个类
- condition queue : 条件队列,属于ConditionObject(Condition实现),这个队列不是必须的,只有在使用了Condition时才会存在,同时,注意一个Condition对应一个condition queue
Node
AQS的队列是通过内部类Node来实现的,每个节点中保存了对应的线程,这里翻译下注释中描述的Node的每种状态的含义
- CANCELLED:值为1,表示当前线程节点已被取消,由于超时或者中断节点线程被取消,节点一旦处于这个状态则不能再更改,取消状态的节点线程不能被阻塞
- SIGNAL:值为-1,表示后继节点中的线程在等待当前节点唤醒,需要进行unpark操作。当前节点的后继节点对应的线程通过park被阻塞,那么当前节点对应的线程在释放锁或者取消操作时应该唤醒其后继节点对应的线程
- CONDITION:值为-2,表示节点处于条件队列中,当其他线程调用了Condition的signal()方法后,CONDITION状态的节点将从条件队列转移到同步队列中,等待获取共享资源
- PROPAGATE:值为-3,共享模式下,前继结点不仅会唤醒其后继结点,同时也可能会唤醒后继的后继结点,进行唤醒传播操作
注意,正常创建的同步队列节点初始化waitStatus为0,等待获取共享资源,waitStatus为正值时就处于CANCELLED状态,处于负值时就处于有效的节点状态,也就是SIGNAL,CONDITION,PROPAGATE其中一种状态
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;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
// 节点状态
volatile int waitStatus;
// 前驱节点
volatile Node prev;
// 后继节点
volatile Node next;
// 节点对应的线程
volatile Thread thread;
// 下一个等待节点
Node nextWaiter;
/**
* 判断节点是否为共享节点
* 添加节点时构造方法会传入节点类型是共享模式还是独占模式
*/
final boolean isShared() {
return nextWaiter == SHARED;
}
/**
* 返回前驱节点,为null时抛出异常
*/
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
}
// addWaiter中使用
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
// addConditionWaiter中使用
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
类定义
从类继承关系可知,AbstractQueuedSynchronizer继承自AbstractOwnableSynchronizer抽象类,并且实现了Serializable接口,可以进行序列化,同时其是一个抽象类,模板方法模式完成主要的同步器框架实现,子类只需要按照自己的需要实现对应的方法即可
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable
子类实现方法
自定义同步器在实现时只需要实现共享资源state的获取与释放即可,我们只需要关心下列5个方法的实现:
- tryAcquire:独占模式尝试获取资源,成功则返回true,失败则返回false
- tryRelease:独占模式尝试释放资源,成功则返回true,失败则返回false
- tryAcquireShared:共享模式尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源
- tryReleaseShared:共享模式尝试释放资源,成功返回true,否则返回false
- isHeldExclusively:该线程是否正在独占资源。只有用到condition才需要去实现它
AbstractOwnableSynchronizer
注释解释到,可以由线程以独占方式拥有的同步器。此类为创建锁和相关同步器提供了基础。AbstractOwnableSynchronizer类本身不管理或使用此信息。但是,子类和工具类可以使用适当维护的值帮助控制和监视访问以及提供诊断,通俗点说就是可以设置独占资源线程和获取独占资源线程,比较简单,当然这样就在监控诊断的时候提供了帮助
public abstract class AbstractOwnableSynchronizer
implements java.io.Serializable {
private static final long serialVersionUID = 3737899427754241961L;
protected AbstractOwnableSynchronizer() { }
/**
* 独占模式同步器下的线程
*/
private transient Thread exclusiveOwnerThread;
/**
* 设置独占线程
* null表明没有线程独占
* 注意下这里没有加锁也没有使用volatile修饰,在子类实现中是通过CAS来控制并发的
*/
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
/**
* 返回通过setExclusiveOwnerThread设置的独占线程
*/
protected final Thread getExclusiveOwnerThread() {
return exclusiveOwnerThread;
}
}
常量/变量
/**
* 同步队列头节点,懒加载模式,除了初始化,只能通过setHead修改
* 假如头节点存在,它的waitStatus保证不是CANCELLED状态
*/
private transient volatile Node head;
/**
* 同步队列尾节点,懒加载模式,除了初始化,只能通过enq方法添加新的节点修改
*/
private transient volatile Node tail;
/**
* 同步状态,线程争抢的就是这个资源
*/
private volatile int state;
/**
* 自旋时间
*/
static final long spinForTimeoutThreshold = 1000L;
// 成员变量内存偏移地址供CAS使用
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); }
}
构造方法
空构造方法,供子类调用,这里注意下对象初始化时state为0
protected AbstractQueuedSynchronizer() { }
重要方法(同步队列)
涉及到Condition条件队列的部分先略过,之后会单独写文章进行分析,下面一起来看下同步队列涉及的方法,其中涉及到获取共享资源的操作方法如下,分为独占和共享两种模式:
方法名 | 说明 |
---|---|
acquire | 独占模式下获取共享资源,不可中断 |
acquireInterruptibly | 独占模式下获取共享资源,可中断 |
tryAcquireNanos | 独占模式下获取共享资源,超时返回,可中断 |
acquireShared | 共享模式下获取共享资源,不可中断 |
acquireSharedInterruptibly | 共享模式下获取共享资源,可中断 |
tryAcquireSharedNanos | 共享模式下获取共享资源,超时返回,可中断 |
释放共享资源的操作方法同样分为独占和共享两种模式:
方法名 | 说明 |
---|---|
release | 独占模式下释放资源 |
releaseShared | 共享模式下释放资源 |
由于方法很多实现是相同的,在获取共享资源的方法里我们关注下acquire和acquireShared即可,其他部分各位可自行查看源码实现
总结
本篇文章仅仅是对AQS的总体介绍,未涉及共享资源获取与释放的源码实现,也未涉及Condition的源码实现,因为其模板方法模式的实现方式,当前类只是抽象类,直接讲解源码可能会令人困惑,之后笔者会专门通过具体子类的实现示例来分析其底层源码对应的方法,本篇只需要理解其数据结构的实现和Node的实现,以及一些常量与变量的意义即可,后面具体分析时有个印象,希望对各位有所帮助
以上内容如有问题欢迎指出,笔者验证后将及时修正,谢谢