AbstractQueuedSynchronizer源码解
本篇文章我们来看一下AbstractQueuedSynchronizer也就是俗称的AQS的源码解析,直接进入正题。
构造方法
AbstractQueuedSynchronizer只有一个无参构造方法,没有什么好说的。
protected AbstractQueuedSynchronizer() { }
内部类
静态内部类Node类,是一个双向链表。
static final class Node {
static final Node SHARED = new Node();//用于指示节点正在共享模式下等待的标记
static final Node EXCLUSIVE = null;//用于指示节点正在以独占模式等待的标记
static final int CANCELLED = 1;//waitStatus值,指示线程已取消
static final int SIGNAL = -1;//waitStatus值,指示后续线程需要取消标记
static final int CONDITION = -2;//waitStatus值,指示线程正在等待条件
static final int PROPAGATE = -3;//waitStatus值,表示下一个获取共享应无条件传播的waitStatus值
volatile int waitStatus;//状态字段,仅接受以上4个值和默认的0
volatile Node prev;//指向当前节点的前置节点
volatile Node next;//指向当前节点的后置节点
volatile Thread thread;//当前节点的线程
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() { //用于建立初始头节点或共享标记
}
Node(Thread thread, Node mode) { //addWaiter方法所使用
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { //用于Condition中使用
this.waitStatus = waitStatus;
this.thread = thread;
}
}
以上是Node类的解析。
参数
头节点,即当前持有锁的线程
private transient volatile Node head;
尾结点,等待队列的尾部,已延迟初始化。仅通过enq方法进行修改以添加新的等待节点。
private transient volatile Node tail;
同步状态,初始为0,使用volatile关键字修饰,保证了其他线程的可见性,当值发生改变是其他线程能够第一时间获取到最新值。
private volatile int state;
方法
compareAndSetState
//通过CAS的方式将同步状态设置为给定的更新值,如果当前状态值等于预期值。
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
enq
private Node enq(final Node node) {
//CAS"自旋",直到成功加入队尾
for (;;) {
Node t = tail;
if (t == null) { //队列为空,创建一个空的标志结点作为head结点,并将tail也指向它。
if (compareAndSetHead(new Node()))
tail = head;
} else {//正常流程,放入队尾
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
addWaiter
private Node addWaiter(Node mode) {
//以给定模式构造结点。mode有两种:EXCLUSIVE(独占)和SHARED(共享)
Node node = new Node(Thread.currentThread(), mode);
// 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);//调用enq方法放入队列
return node;
}
setHead
将队列的头设置为节点,从而出列。仅由调用获取方法。为了GC,还将未使用的字段清空并且抑制不必要的信号和遍历
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}
acquireQueued
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;//标记是否成功拿到资源
try {
boolean interrupted = false;//标记等待过程中是否被中断过
//又是一个“自旋”!
for (;;) {
final Node p = node.predecessor();//拿到前驱节点
//如果前驱是head,即该结点已成老二,那么便有资格去尝试获取资源(可能是老大释放完资源唤醒自己的,当然也可能被interrupt了)
if (p == head && tryAcquire(arg)) {
setHead(node);//拿到资源后,将head指向该结点。所以head所指的标杆结点,就是当前获取到资源的那个结点或null
p.next = null; //setHead中node.prev已置为null,此处再将head.next置为null,就是为了方便GC回收以前的head结点。也就意味着之前拿完资源的结点出队了
failed = false;// 成功获取资源
return interrupted;//返回等待过程中是否被中断过
}
//如果自己可以休息了,就通过park()进入waiting状态,直到被unpark()。如果不可中断的情况下被中断了,那么会从park()中醒过来,发现拿不到资源,从而继续进入park()等待
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed) // 如果等待过程中没有成功获取资源(如timeout,或者可中断的情况下被中断了),那么取消结点在队列中的等待。
cancelAcquire(node);
}
}
先看看shouldParkAfterFailedAcquire()和parkAndCheckInterrupt()具体干些什么
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;//拿到前驱的状态
if (ws == Node.SIGNAL)//如果已经告诉前驱拿完号后通知自己一下,那就可以安心休息了
return true;
if (ws > 0) {
/*
* 如果前驱放弃了,那就一直往前找,直到找到最近一个正常等待的状态,并排在它的后边。
* 注意:那些放弃的结点,由于被自己“加塞”到它们前边,它们相当于形成一个无引用链,稍后就会被GC回收
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//如果前驱正常,那就把前驱的状态设置成SIGNAL,告诉它拿完号后通知自己一下。有可能失败,人家说不定刚刚释放完呢
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
park()会让当前线程进入waiting状态。在此状态下,有两种途径可以唤醒该线程:1)被unpark();2)被interrupt()
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);//调用park()使线程进入waiting状态
return Thread.interrupted();//如果被唤醒,查看自己是不是被中断的
}
看了shouldParkAfterFailedAcquire()和parkAndCheckInterrupt(),现在让我们再回到acquireQueued(),总结下该函数的具体流程:
- 结点进入队尾后,检查状态,找到安全休息点;
- 调用park()进入waiting状态,等待unpark()或interrupt()唤醒自己;
- 被唤醒后,看自己是不是有资格能拿到号。如果拿到,head指向当前结点,并返回从入队到拿到号的整个过程中是否被中断过;如果没拿到,继续流程1。
acquire
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
- 调用自定义同步器的tryAcquire()尝试直接去获取资源,如果成功则直接返回;
- 没成功,则addWaiter()将该线程加入等待队列的尾部,并标记为独占模式;
- acquireQueued()使线程在等待队列中休息,有机会时(轮到自己,会被unpark())会去尝试获取资源。获取到资源后才返回。如果在整个等待过程中被中断过,则返回true,否则返回false。
- 如果线程在等待过程中被中断过,它是不响应的。只是获取资源后才再进行自我中断selfInterrupt(),将中断补上
release
public final boolean release(int arg) {
//tryRelease这个方法是需要独占模式的自定义同步器去实现的。正常来说,tryRelease()都会成功的,因为这是独占模式,该线程来释放资源,那么它肯定已经拿到独占资源了,直接减掉相应量的资源即(state-=arg),也不需要考虑线程安全的问题。但要注意它的返回值,上面已经提到了,release()是根据tryRelease()的返回值来判断该线程是否已经完成释放掉资源了!所以自义定同步器在实现时,如果已经彻底释放资源(state=0),要返回true,否则返回false。
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
此方法用于唤醒等待队列中下一个线程
private void unparkSuccessor(Node node) {
//这里,node一般为当前线程所在的结点。
int ws = node.waitStatus;
if (ws < 0)//置零当前线程所在的结点状态,允许失败。
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;//找到下一个需要唤醒的结点s
if (s == null || s.waitStatus > 0) {//如果为空或已取消
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)// 从后向前找
if (t.waitStatus <= 0)//从这里可以看出,<=0的结点,都是还有效的结点
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);//唤醒
}
这个函数并不复杂。一句话概括:用unpark()唤醒等待队列中最前边的那个未放弃线程,这里我们也用s来表示吧
以上就是AbstractQueuedSynchronize常用的方法,同时ReentrantLock的加锁和释放锁都是基于以上方法。