其实上面的Node类可以直接拷贝出来当成一个新建的类,然后尝试构建一个双向链表自行调试,这样子就能深刻它的数据结构。例如:
public class AqsNode { static final AqsNode SHARED = new AqsNode(); static final AqsNode EXCLUSIVE = null; static final int CANCELLED = 1; static final int SIGNAL = -1; static final int CONDITION = -2; static final int PROPAGATE = -3; volatile int waitStatus; volatile AqsNode prev; volatile AqsNode next; volatile Thread thread; AqsNode nextWaiter; final boolean isShared() { return nextWaiter == SHARED; } final AqsNode predecessor() { AqsNode p = prev; if (p == null) throw new NullPointerException(); else return p; } AqsNode() { } AqsNode(AqsNode nextWaiter) { this.nextWaiter = nextWaiter; THREAD.set(this, Thread.currentThread()); } AqsNode(int waitStatus) { WAITSTATUS.set(this, waitStatus); THREAD.set(this, Thread.currentThread()); } final boolean compareAndSetWaitStatus(int expect, int update) { return WAITSTATUS.compareAndSet(this, expect, update); } final boolean compareAndSetNext(AqsNode expect, AqsNode update) { return NEXT.compareAndSet(this, expect, update); } final void setPrevRelaxed(AqsNode p) { PREV.set(this, p); } private static final VarHandle NEXT; private static final VarHandle PREV; private static final VarHandle THREAD; private static final VarHandle WAITSTATUS; static { try { MethodHandles.Lookup l = MethodHandles.lookup(); NEXT = l.findVarHandle(AqsNode.class, "next", AqsNode.class); PREV = l.findVarHandle(AqsNode.class, "prev", AqsNode.class); THREAD = l.findVarHandle(AqsNode.class, "thread", Thread.class); WAITSTATUS = l.findVarHandle(AqsNode.class, "waitStatus", int.class); } catch (ReflectiveOperationException e) { throw new ExceptionInInitializerError(e); } } public static void main(String[] args) throws Exception { AqsNode head = new AqsNode(); AqsNode next = new AqsNode(AqsNode.EXCLUSIVE); head.next = next; next.prev = head; AqsNode tail = new AqsNode(AqsNode.EXCLUSIVE); next.next = tail; tail.prev = next; List<Thread> threads = new ArrayList<>(); for (AqsNode node = head; node != null; node = node.next) { threads.add(node.thread); } System.out.println(threads); } } // 某次执行的输出: [null, Thread[main,5,main], Thread[main,5,main]]实际上,AQS中一共存在两种等待队列,其中一种是普通的同步等待队列,这里命名为Sync Queue,另一种是基于Sync Queue实现的条件等待队列,这里命名为Condition Queue。
理解同步等待队列前面已经介绍完AQS的同步等待队列节点类,下面重点分析一下同步等待队列的相关源码,下文的Sync队列、Sync Queue、同步队列和同步等待队列是同一个东西。首先,我们通过分析Node节点得知Sync队列一定是双向链表,AQS中有两个瞬时成员变量用来存放头节点和尾节点:
// 头节点引用(注意由transient volatile修饰,不会序列化,并且写操作会马上刷新到主内存) private transient volatile Node head; // 尾节点引用(注意由transient volatile修饰,不会序列化,并且写操作会马上刷新到主内存) private transient volatile Node tail; // 变量句柄相关,用于CAS操作头尾节点 private static final VarHandle STATE; private static final VarHandle HEAD; private static final VarHandle TAIL; static { try { MethodHandles.Lookup l = MethodHandles.lookup(); STATE = l.findVarHandle(AbstractQueuedSynchronizer.class, "state", int.class); HEAD = l.findVarHandle(AbstractQueuedSynchronizer.class, "head", Node.class); TAIL = l.findVarHandle(AbstractQueuedSynchronizer.class, "tail", Node.class); } catch (ReflectiveOperationException e) { throw new ExceptionInInitializerError(e); } // 确保LockSupport类已经初始化 - 这里应该是为了修复之前一个因为LockSupport未初始化导致的BUG Class<?> ensureLoaded = LockSupport.class; } // 初始化同步队列,注意初始化同步队列的时候,头尾节点都是指向同一个新的Node实例 private final void initializeSyncQueue() { Node h; if (HEAD.compareAndSet(this, null, (h = new Node()))) tail = h; } // CAS设置同步队列的尾节点 private final boolean compareAndSetTail(Node expect, Node update) { return TAIL.compareAndSet(this, expect, update); } // 设置头节点,重点注意这里:传入的节点设置成头节点之后,前驱节点和持有的线程会置为null,这是因为: // 1.头节点一定没有前驱节点。 // 2.当节点被设置为头节点,它所在的线程一定是已经解除了阻塞。 private void setHead(Node node) { head = node; node.thread = null; node.prev = null; }