简述:AQS(AbstractQueuedSynchronizer)抽象的队列同步器,其是 JUC 包众多锁机制和信号量机制的基础,例如 ReentrantLock、Semaphore、CountDownLatch、ReadWriteLock、CyclicBarrier 底层的同步互斥操作都建立在 AQS 之上。AQS 的本质是一个采用双向链表实现的 FIFO 线程等待队列,等待队列上的线程释放和等待都依据一个整型的信号量 volatile int state ,可以将这个信号量视为各个线程所竞争的 "资源"。如下图所示
AQS定义了一系列操作这个等待队列的方法规范,需要定制化采用 AQS 来实现同步器亦或是自定义规则的锁,可以先继承 AQS 这个类,然后重写它对于 state 变量的一系列操作(模板方法模式)。这些你可以在 ReentrantLock 的源码里面一探究竟。
资源共享方式:AQS 提供了两种资源共享的方式,即独占(Exclusive)和共享(Share),独占的例子有 ReentrantLock,共享的例子有 CountDownLatch、ReadWriteLock。
学习简述:
1、首先线程进入队列等待需要 AQS 配套实现如何将线程挂起和唤醒,底层用到的是 LockSupport 工具类,里面提供了 pack() 和 unpack() 两个 native 方法来进行线程的挂起和唤醒
2、等待队列的 Node 都包含了什么信息?其主要的当然包含了线程信息、线程状态信息、节点前驱后继等,强烈建议看源码(英文注释十分之好)
static final class Node {
static final Node SHARED = new Node();//表示是共享模式,源码里面的nextWaiter字段有注释说明了这个 special value
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; //存储在condition队列中的后继节点
//是否为共享锁
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
}
//将线程构造成一个Node,添加到等待队列
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
//这个方法会在Condition队列使用,后续单独写一篇文章分析condition
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
3、除了同步队列,在独占模式下,还可以使用 Condition 来让线程进入条件队列等待。说白了,就是你可以视 AQS 里面有两个队列,一个是同步队列,里面的线程等待调度获取资源,而条件队列是针对某一条件来让线程进行等待唤醒。当位于条件队列的线程被唤醒之后会被转化到同步队列中去,源码里面有个方法 transferForSignal 就是干这个事情的!而 AQS 中的 Node 为两用,同时可作为同步队列的节点也可以作为条件队列的节点,可以看源码说明。
4、AQS 是个模板方法模式的经典体现,定义了一些类规则方法或者说接口,让这些具体的方法延续到子类(即各种锁或者信号量)去实现,那么具体是怎么进行实现和操作的可以看别人的博客,但是更重要的是,博客的说明一定要自己进去源码一探究竟,这样就有更深的体会
下面参考链接: