一.AQS介绍
AbstractQueuedSynchronizer 它是java.util包实现的基础,甚至可以说它就是锁,相比于Synchronzied关键字实现的内置锁,只实现了排他锁。AQS通过一个类变量 private int state 来表示锁状态,通过继承类实现了多种锁模式,如排它锁,共享锁,以及可重入锁等。
在AQS中通过一个内部类Node 来包装线程,代码如下
static final class Node {
/** 用来标志线程处在共享模式,临界区可由多个线程进入 */
static final Node SHARED = new Node();
/** 用来标志线程处在排他模式 */
static final Node EXCLUSIVE = null;
//用来代表当前线程所处状态
volatile int waitStatus;
/** waitStatus 的标志位表示,这个线程已经不可用了
这里很巧秒的只有 cancelled > 0 即状态大于0,说明线程不可用了 */
static final int CANCELLED = 1;
/** 表示后继线程需要被唤醒 */
static final int SIGNAL = -1;
/** 表示线程正处在条件等待中 */
static final int CONDITION = -2;
/**
共享锁中的线程状态
*/
static final int PROPAGATE = -3;
//当前节点前驱
volatile Node prev;
/**
* 当前节点的后继
*/
volatile Node next;
/**
* 包装的线程
*/
volatile Thread thread;
/**
用来存储当前线程所在的模式(共享或者排他)
*/
Node nextWaiter;
/**
* 如果当前线程正处在共享模式就返回true,判断依据是nextWaiter == shared
*/
final boolean isShared() {
return nextWaiter == SHARED;
}
/**
* 返回节点前继
*/
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { // 这个构造方法是用来标识共享模式节点的或者作为阻塞队列头节点。
}
Node(Thread thread, Node mode) { // 用来加入到阻塞队列中去
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // 用来加入到条件队列中去
this.waitStatus = waitStatus;
this.thread = thread;
}
}
看完上面Node类的分析,其中出现了两个队列 等待队列 和 条件队列,这里先把结果抛出来,AQS内部随着会有一个等待队列和多个条件队列。其中阻塞队列由AQS两个内部变量
private transient volatile Node head;
条件队列头节点,这个节点除了初始化时候,采用一个new Node()空节点代替,在这之后,头节点都是当前占有锁资源的节点。private transient volatile Node tail;
条件队列尾节点,只能由插入节点的时候修改。
组成的,等待队列中的节点(除了头节点)都通过自旋的方式,等待被唤醒,获取锁资源。
而条件阻塞队列是由AQS的另一个内部类ConditionObject来决定,条件队列也是由两个节点构成的
private transient Node firstWaiter;
private transient Node lastWaiter;
在用Synchronized关键字作为锁的时候,线程随条件进入阻塞池中,唤醒的时候是随机唤醒的。而用AQS作为锁的时候,就是通过ConditionObject来实现随条件阻塞,因为是存放在条件队列中,因此其中的节点唤醒也是可控制的。另外节点被唤醒后就会插入到等待队列中去。
到这里,已经讲清楚了AQS是如何作为显示锁的,以及其中两个关键队列。
显示锁的使用,就要放到它具体的实现中去讲了。