队列同步器AQS(AbstractQueuedSynchronizer)
源码解析
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
//指向队列中的头节点
private transient volatile Node head;
//指向队列中的尾部节点
private transient volatile Node tail;
//当前状态
private volatile int state;
}
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;
// 节点在等待队列中,线程在等待在Condition 上,其他线程对Condition调用singnal()方法后,该节点加入到同步队列中。
static final int CONDITION = -2;
// 表示下一次共享式获取同步状态的时会被无条件的传播下去。
static final int PROPAGATE = -3;
/**等待状态*/
volatile int waitStatus;
/**前驱节点 */
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() { // 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;
}
}
从源码可以看出AQS的数据结构为:双向链表和Redis的链表结构类似
知道了底层的数据结构,我们来看实现的方法
入列
//作用:构造节点以及加入到队列里面
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
//快速加入
Node pred = tail;
if (pred != null) {
node.prev = pred;
//从CAS来设置加入到队列里面
//这里node和tail进行了绑定
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) {
//如果队列为空的话,则必须主动创建一个队列
//这里通过unsafe来新建对象
if (compareAndSetHead(new Node()))
tail = head;
} else {
//这里加入队列的节点已经是队列中的第二个节点
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
compareAndSetHead(new Node()底层C++源码
● 通过obj+offset来寻找到要判断的对象
● 然后进行对象匹配,如果匹配的话,那么就把该地址的值改成新的
// Unsafe.h
virtual jboolean compareAndSwapObject(::java::lang::Object *, jlong, ::java::lang::Object *, ::java::lang::Object *);
// natUnsafe.cc
static inline bool
compareAndSwap (volatile jobject *addr, jobject old, jobject new_val)
{
jboolean result = false;
spinlock lock;
// 如果字段的地址与期望的地址相等则将字段的地址更新
if ((result = (*addr == old)))
*addr = new_val;
return result;
}
// natUnsafe.cc
jboolean
sun::misc::Unsafe::compareAndSwapObject (jobject obj, jlong offset,
jobject expect, jobject update)
{
// 获取字段地址并转换为字符串
jobject *addr = (jobject*)((char *) obj + offset);
// 调用 compareAndSwap 方法进行比较
return compareAndSwap (addr, expect, update);
}
● 当没有节点的时候,会调用compareAndSetHead(new Node())方法,在头节点的位置设置一个头节点并且尾节点也指向这个新节点
● 当头节点和尾节点存在的时候,开始添加新的节点到队列里面,也就是tail !=null
● 首先 新建一个node指向尾节点,要加入的节点指向原来的尾节点,并且用CAS让尾节点指向新节点,此时尾节点和Node节点处于双向绑定的状态,二者指向同一对象。
● 最后 将t的next节点(原来的尾节点)指向新节点。
● 当节点越来越多的时候就编变成来上述情况。
出列
//将队列头设置为节点,从而退出队列。仅由acquire方法调用。为了GC和抑制不必要的信号和遍历,还将未使用的字段空出来。
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}