#总体介绍
基于队列的抽象同步器,它是jdk中所有显示的线程同步工具的基础,像ReentrantLock/DelayQueue/CountdownLatch等等,都是借助AQS实现的。Java中已经有了synchronized关键字,那么为什么还需要来这么一出呢?因为AQS能实现更多维度,更多场景的锁机制,例如共享锁(读锁)/基于条件的线程阻塞/可以实现公平和非公平的锁策略/可以实现锁等待的中断,而synchronized关键字由JVM实现,在代码使用层面来说,如果仅仅是使用独占锁,那synchronized关键字比其它的锁实现用起来方便。
下面步入正题,来看看AQS都提供了哪些能力。
#1、主体结构
先来看看内部的主体结构:
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
protected AbstractQueuedSynchronizer() { }
//内部类Node,代表每一个获取或者释放锁的线程
static final class Node{}
//head 和 tail构成了同步队列链表的头节点和尾节点。
private transient volatile Node head;
private transient volatile Node tail;
//同步状态值,所有的同步行为都是通过state这个共享资源来实现的。
private volatile int state;
//条件对象,用于同步在当前锁对象上的线程
public class ConditionObject implements Condition, java.io.Serializable{}
/******
一系列的内部方法:
包括尝试获取锁权力
尝试获取锁失败后构造节点放入等待队列
各种入队出队的操作
尝试释放锁等
********/
//一系列的Unsafe操作
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;
}
所以总结起来AQS就是通过一系列的Unsafe方法去操作一个链表队列,而链表队列中每个节点需要操作的共享资源就是一个int state字段,如果要用到条件等待,则需要了解ConditionObject。
#2、节点类的构造
再来看看Node节点的内部结构:
static final class Node {
//常量,见nextWaiter属性
static final Node SHARED = new Node();
static final Node EXCLUSIVE = null;
//waitStatus 常量值。表示等待获取锁的线程已经被取消(线程中断/等待超时)
static final int CANCELLED = 1;
//waitStatus 常量值。表示后继线程需要unpark
static final int SIGNAL = -1;
//waitStatus 常量值。表示线程正在Condition队列中
static final int CONDITION = -2;
//waitStatus 常量值。表示线程的acquireShared行为需要无条件的向队列的下一个节点传递。用在共享锁的场景。
static final int PROPAGATE = -3;
//注意,waitStatus除了以上常量值以为,由于是int类型,则默认是0
volatile int waitStatus;
//前继节点
volatile Node prev;
//后继节点
volatile Node next;
//节点所代表的线程
volatile Thread thread;
//如果节点再Condition等待队列中,则该字段指向下一个节点,如果节点在同步队列中,则为一个标志位,其值为 SHARED或者null
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
}
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
所以在同步队列中Node应该长下面这个样子:
在Condition等待队列中应该长如下这个样子:
Node节点一共有3个构造方法他们分别在同步队列初始化时/加入同步队列/加入Condition队列时使用,结合构造方法以及AQS和Condition类,我们看下最终队列会长什么样子。
首先看一下无参的构造方法在什么地方使用:
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode); //用当前线程和mode类型构造节点,此时waitStatus默认为0
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
addWaiter方法是将线程放入队列的方法,开始构造new Node(Thread.currentThread(), mode)这样的节点,然后尝试入队列,如果入队列失败则进入enq方法,在enq中,如果队列为空(当t==null的时候,因为入队操作是从队尾进行的),此时构造了一个空的Node节点,然后将head和tail指向它。我们称这个节点为哨兵节点,为什么需要这样一个节点,后面会说明,哨兵节点入队列后我们通过当前线程构造的的节点new Node(Thread.currentThread(), mode)再入队列。由于构造方法没有传入waitStatus值,所以此时waitStatus默认为0。
所以综合AQS来看,最终的同步队列应该长如下这个样子:
那么在Condition等待队列中呢? Condition队列中有firstWaiter和lastWaiter分别指向头节点和尾节点。
先看下加入Condition队列的代码:
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
//构造waitStatus等于CONDITION的NODE节点
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null) //如果队列为空,则新节点入队列,头节点指向新节点
firstWaiter = node;
else
t.nextWaiter = node; //如果队列不为空,则新节点加入到队列末尾
lastWaiter = node; //尾节点指向新节点
return node;
}
所以,最终Condition队列中的节点如下:
Condition队列中的节点是单向的,并且没有哨兵节点,在Condition队列中,nextWaiter指向下一个节点,而不是像在同步队列中那样指向模式(SHARED, null),其中各个节点的waitStatus等于CONDITION(-2)。
3、获取锁
##3.1 首先看写锁lock的实现
public void lock() {
sync.acquire(1); //调用同步器的acquire方法
}
这里同步器由ReentrantReadWriteLock来继承AQS后具体实现,不同的锁对AQS的实现是不一样的。
public final void acquire(int arg) {
if (!tryAcquire(arg) && //尝试获取锁失败
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) //并且节点入队列成功
selfInterrupt(); //线程中断
}
整个acquire方法其实就是3部曲,①尝试获取锁 ②如果获取锁没有成功,则构造节点加入等待队列中 ③如果节点入队列成功,则线程自我中断让出资源。当线程进入同步队列中后就实现了获取锁过程中的线程阻塞功能了,因为这个时候线程是自我中断状态。
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}