深剖「AQS(2)」

125 阅读2分钟

这是我参与8月更文挑战的第20天,活动详情查看:8月更文挑战

队列及state

AQS的队列操作

入队

  • 当线程获取锁失败以后,那么这个线程就会被转换为Node节点
  • 然后使用enq(final Node node)将该节点插入到AQS的阻塞队列中
private Node enq(final Node node) {
    for (;;) {
    // 节点指向尾部节点
        Node t = tail;
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
            // 使用CAS设置一个烧饼节点为头结点
                tail = head;
        } else {
        // 设置node的前驱节点为尾部节点
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}
  • 在第一次循环的时候t为null,t指向了tail和head

image.png

  • 当第二次循环的时候,利用cas去设置node节点为尾结点

image.png

  • 此时便成为了双线链表的插入

state把变量

  • 重点放在await()signal() await
  • await方法当被调用的时候,背部会构造一个Node.CONDITION的node节点
  • 然后把该节点插入到条件队列末尾,再释放获取的锁,再阻塞被挂起 image.png
  1. 创建新的node节点,并且插入到条件队列末尾
  2. 释放当前的线程获取的锁(看到release要注意📢)
  3. 调用park方法阻塞挂起当前线程

当被挂起之后如何唤醒呢 signal

image.png

  • 1是将条件队列头元素移动到AQS队列
  • 下面来看await中放入条件队列

image.png

  1. 创建一个类型为Node.CONDITION的节点,然后通过下面代码进行在单项队列尾部插入一个元素
  • 要是多个线程一起调用lock.lock(),只有一个线程能获得锁,其他的就会转换为Node节点
  • 插入lock锁对应的AQS队列里面,并且使用CAS自旋尝试获取锁
  • 如果获取到锁🔐的线程又调用了await方法,那么该线程就会释放获取到的锁,并且转换为Node节点插入到条件变量对应的条件队列里面(可以理解为「自投罗网」)

总结

一个锁对应一个AQS阻塞队列,对应多个条件变量,每个条件变量又有自己的一个条件队列✅