AQS就是AbstractQueuedSynchronizer,它是为实现依赖于先进先出(FIFO)等待队列的阻塞锁和相关同步器(信号量,事件等)提供的一个框架。AQS继承了AbstractOwnableSynchronizer类,这个类为创建锁和相关同步器提供了基础。AQS是Concurrent包的核心,lock就是在AQS的基础上实现的,阻塞队列,线程池,信号量等都离不开AQS的支持。
CLH
CLH lock queue通常被用来处理并发的情况,它通过双向队列(FIFO)来完成同步状态。每个线程都会被封装成一个Node节点放到同步队列中。
每个Node节点保存了当前线程的同步状态,等待状态,前驱和后继节点等。
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;
// 线程的等待状态 表示线程在Condtion上
static final int CONDITION = -2;
// 表示下一个acquireShared需要无条件的传播
static final int PROPAGATE = -3;
/**
* SIGNAL: 当前节点的后继节点处于等待状态时,如果当前节点的同步状态被释放或者取消,
* 必须唤起它的后继节点
*
* CANCELLED: 一个节点由于超时或者中断需要在CLH队列中取消等待状态,被取消的节点不会再次等待
*
* CONDITION: 当前节点在等待队列中,只有当节点的状态设为0的时候该节点才会被转移到同步队列
*
* PROPAGATE: 下一次的共享模式同步状态的获取将会无条件的传播
* waitStatus的初始值时0,使用CAS来修改节点的状态
*/
volatile int waitStatus;
/**
* 当前节点的前驱节点,当前线程依赖它来检查waitStatus,在入队的时候才被分配,
* 并且只在出队的时候才被取消(为了GC),头节点永远不会被取消,一个节点成为头节点
* 仅仅是成功获取到锁的结果,一个被取消的线程永远也不会获取到锁,线程只取消自身,
* 而不涉及其他节点
*/
volatile Node prev;
/**
* 当前节点的后继节点,当前线程释放的才被唤起,在入队时分配,在绕过被取消的前驱节点
* 时调整,在出队列的时候取消(为了GC)
* 如果一个节点的next为空,我们可以从尾部扫描它的prev,双重检查
* 被取消节点的next设置为指向节点本身而不是null,为了isOnSyncQueue更容易操作
*/
volatile Node next;
/**
* 当前节点的线程,初始化后使用,在使用后失效
*/
volatile Thread thread;
/**
* 链接到下一个节点的等待条件,或特殊的值SHARED,因为条件队列只有在独占模式时才能被访问,
* 所以我们只需要一个简单的连接队列在等待的时候保存节点,然后把它们转移到队列中重新获取
* 因为条件只能是独占性的,我们通过使用特殊的值来表示共享模式
*/
Node nextWaiter;
/**
* 如果节点处于共享模式下等待直接返回true
*/
final boolean isShared() {
return nextWaiter == SHARED;
}
/**
* 返回当前节点的前驱节点,如果为空,直接抛出空指针异常
*/
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { // 用来建立初始化的head 或 SHARED的标记
}
Node(Thread thread, Node mode) { // 指定线程和模式的构造方法
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // 指定线程和节点状态的构造方法
this.waitStatus = waitStatus;
this.thread = thread;
}
}
AQS具有头节点和尾节点
private transient volatile Node head;
private transient volatile Node tail;
Node是构成同步队列的基础,看一下Node的结构
同步队列中首节点是获取到锁的节点,它在释放的时候会唤醒后继节点,后继节点获取到锁的时候,会把自己设为首节点。
注意,设置首节点不需要使用CAS,因为在并发环境中只有一个线程都获取到锁,只有获取到锁的线程才能设置首节点。
独占式
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
通过调用acquire的方法获取同步状态,该方法忽略中断,线程获取同步状态失败后,进入同步队列,在对其进行中断操作后,线程不会从同步队列移除。首先调用tryAcquire方法获取同步状态,AQS并没有实现这个方法,具体的实现由它的继承类进行重写,比如ReentrantLock的Sync类。如果获取同步状态成功的直接返回true;如果获取同步状态失败的话,调用addWaiter方法把线程封装成一个Node节点添加到同步队列的尾部,最后调用acquireQueued方法使节点以自旋的方式获取同步