AQS的线程等待队列实现原理

126 阅读3分钟

  AbstractQueuedSynchronizer(AQS)提供了一套可用于实现锁同步机制的框架,不夸张地说,AQS是JUC同步框架的基石。AQS通过一个FIFO队列维护线程同步状态,实现类只需要继承该类,并重写指定方法即可实现一套线程同步机制。

  AQS根据资源互斥级别提供了独占和共享两种资源访问模式;同时其定义Condition结构提供了wait/signal等待唤醒机制。在JUC中,诸如ReentrantLock、CountDownLatch等都基于AQS实现。   ASQ中存在两个对象,对象一是AQS实例对象,对象二是实例对象内存储的阻塞队列(头节点和尾节点);

  1. 初始化阻塞队列: 头节点为无线程的空节点(new Node()),尾节点指向头节点。
  2. 第一个加入阻塞队列的节点(new Node(Thread)),形成head<->node...

  本文主要讲解AQS中,线程遇到阻塞情况,加入到线程等待队列的原理,下面是线程获取独占锁的部分源码:

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

  本次不讲解tryAcquire尝试获取锁的代码,只讨论当线程获取锁失败,尝试加入等待队列的情景。 addWaiter函数负责将新增目标节点添加到当前阻塞队列的队尾,下面是源码:

private Node addWaiter(Node mode) {
    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);
    return node;
}

   上述源码中tail指的是锁对象持有链表的尾节点,pred则真正代表了链表的尾节点,并参与了增链。

  1. Node node = new Node(Thread.currentThread(), mode);负责根据当前线程创建一个节点,该节点将会被添加到队列末尾。
  2. Node pred = tail; 将当前锁对象存储的尾节点赋值给pred
  3. if (pred != null) 若队列末尾节点为空,则认为队列是空的,通过enq 进行队列初始化。
  4. node.prev = pred;将目标节点node挂载在尾节点pred的尾部,node->pred;此处保证了逆向链表的准确性,此时会出现多个目标node同时指向pred
  5. if (compareAndSetTail(pred, node)),该函数通过CAS判断,若当前线程获取的pred与锁对象中tail位置存储的尾节点是否一致,一致的话将锁对象的tail未知换成node(其他线程此时手持的是上一个尾节点pred,与现在的尾节点node自然不一致)。
  6. pred.next = node; pred是链表的尾节点,指向node,形成双链表pred<->node
  7. return node;并将node节点返回,node则是当前链表的尾节点。

image.png

在队列是空的情况下,enq函数负责初始化等待队列,并将目标节点添加其中。以下是enq的源码:

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;
            }
        }
    }
}

  该函数内置一个无限循环,当尾节点是空的时候(t == null),通过CAS在head位置放置一个空节点(new Node(),无线程不会参与竞争锁),同时将尾节点设置成头节点; 此初始化过程意味着队列中有一个个空节点,不是实质存储线程参与竞争的节点;此时阻塞队列已经形成。