Java AbstractQueueSynchronizer(AQS)源码总结从数据结构层面理解AQS

本文详细介绍了Java并发编程中的AbstractQueueSynchronizer(AQS)的原理,包括其子类需要实现的方法、队列节点结构、队列和条件队列的操作。AQS是一个用于构建锁和同步器的框架,通过维护一个FIFO的等待队列,支持独占和共享模式的锁获取。文章深入剖析了AQS的插入、获取、释放锁等关键操作,并展示了如何通过AQS实现线程的等待和唤醒。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

AQS简介

  1. AQS是 AbstractQueueSynchronizer 虚拟队列同步器,是一个虚拟类,子类只需实现其5个方法中的三个就可以构造一个可获取独占锁或获取共享锁的类,没有获取到锁的线程会进入队列排队
  2. 这个类负责实现排队操作,队列中每个节点内部都有一个阻塞的线程,队头的节点是获取锁的线程,队头节点负责唤醒后续一个节点
  3. 其父类 AbstractOwnableSynchronizer 负责记录和设置当前获取锁的线程
    AbstractOwnableSynchronizer 源码可以看我这篇文章 AbstractOwnableSynchronizer
  4. 光看AQS操作可能有些抽象,也不知道它在干嘛,可以看看其子类的实现如 ReentrantLock
    ReentrantLock 源码可以看我这篇文章 ReentrantLock

AQS子类需要实现的方法

方法名 作用
boolean tryAcquire(int arg) 尝试获取独占锁,没有成功则排队
boolean tryRelease(int arg) 尝试释放独占锁
iint tryAcquireShared(int arg) 尝试获取共享锁,没有成功则排队
boolean tryReleaseShared(int arg) 尝试释放共享锁
boolean isHeldExclusively() 判断当前线程是否独占锁

AQS构成

AQS队列节点

  1. SHARED 将该队列标记为共享模式即多个线程可以获取到锁,EXCLUSIVE 将该队列标记为独占模式即只有单个线程可以获取到锁
  2. CANCELLED、SIGNAL、CONDITION、PROPAGATE都是节点的状态值,它们会把值赋给waitStatus
  3. prev、next说明AQS是由双向链表实现的队列
  4. thread说明每个Node节点对应一个线程,说明有一个线程在里面阻塞并排队
  5. nextWaiter是下一个条件队列节点
  6. NEXT、PREV、THREAD、WAITSTATUS是变量句柄,是JDK9新特性,通过它们可以对应的变量进行CAS操作和原子操作,不需要再像以前把变量定义为原子类变量了,如NEXT可以对next进行CAS和原子性操作,如果想要达到这种效果只有把prev定义为AtomicReference< Node > 类型
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;
    static final int CONDITION = -2;
    static final int PROPAGATE = -3;
    volatile int waitStatus;
    volatile Node prev;
    volatile Node next;
    volatile Thread thread;
    Node nextWaiter;
    
    private static final VarHandle NEXT;
    private static final VarHandle PREV;
    private static final VarHandle THREAD;
    private static final VarHandle WAITSTATUS;
}

AQS队列

  1. head就是队列的头节点,tail就是队列的尾节点,state在其子类实现一般理解为获取锁的次数
  2. STATE、HEAD、TAIL 是对应变量的变量句柄
public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
   
   
    
  	private transient volatile Node head;
  	private transient volatile Node tail;
	private volatile int state;

	private static final VarHandle STATE;
    private static final VarHandle HEAD;
    private static final VarHandle TAIL;
}

条件队列

可以看出条件队列里面也是使用Node节点作为其组成,只是使用的是Node节点的nextWaiter属性,相当于单向链表

public class ConditionObject implements Condition, java.io.Serializable {
   
   
	 private transient Node firstWaiter;
	 private transient Node lastWaiter;
}

操作

查询操作

get操作(获取)

getExclusiveQueuedThreads操作

作用:获取队列中处于独占锁模式的线程列表
实现:从双向链表尾部开始查找,尾部查找原因在插入enq里面,如果Node节点不是共享节点,则取出节点中存放的线程并将其加入列表

public final Collection<Thread> getExclusiveQueuedThreads() {
   
   
    ArrayList<Thread> list = new ArrayList<>();
    for (Node p = tail; p != null; p = p.prev) {
   
   
        if (!p.isShared()) {
   
   
            Thread t = p.thread;
            if (t != null)
                list.add(t);
        }
    }
    return list;
}
getFirstQueuedThread操作

作用:获取队列中第一个等待的线程
实现:如果头节点等于尾结点说明没有线程排队,否则执行fullGetFirstQueuedThread返回队头后面一个结点内部的线程,或者后续结点内部的线程

public final Thread getFirstQueuedThread() {
   
   
    // handle only fast path, else relay
    // 仅处理快速路径,否则为转发
    return (head == tail) ? null : fullGetFirstQueuedThread();
}
fullGetFirstQueuedThread操作
  1. 连续检查两次,队头h不为空 ,且队头下一个节点s也不为空,且s的前一个节点为队头h,且s存储的线程也不为空,则返回队头下一个节点的线程st
  2. 为什么不返回队头节点内部的线程,因为队头节点内部的线程一般为null,为null表示已经获取到锁在运行了,不为null说明仍然在排队
  3. 如果以上条件不满足,则从队列尾部开始往前找,找到最靠近队头的一个内部线程不为空的节点,再返回该节点内部的线程
private Thread fullGetFirstQueuedThread() {
   
   
        Node h, s;
        Thread st;
        if (  //队头不为空  且 队头下一个也不为空 且队头的下一个的前一个为队头 且存储的线程也不为空
            ((h = head) != null && (s = h.next) != null &&
             s.prev == head && (st = s.thread) != null)
                ||
            //队头不为空  且 队头下一个也不为空 且队头的下一个的前一个为队头 且存储的线程也不为空
            ((h = head) != null && (s = h.next) != null &&
             s.prev == head && (st = s.thread) != null)
            )
            return st;
            
        Thread firstThread = null;
        // 则从队列尾部开始往前找,找到最靠近队头的一个内部线程不为空的节点
        for (Node p = tail; p != null && p != head; p = p.prev) {
   
   
            Thread t = p.thread;
            if (t != null)
                firstThread = t;
        }
        return firstThread;
    }
getQueuedThreads操作

作用:获取队列中等待的线程集合
实现:从双向链表尾部开始查找,如果Node节点内部线程不为空,则取出节点中存放的线程并将其加入列表

 public final Collection<Thread> getQueuedThreads() {
   
   
    ArrayList<Thread> list = new ArrayList<>();
    for (Node p = tail; p != null; p = p.prev) {
   
   
        Thread t = p.thread;
        if (t != null)
            list.add(t);
    }
    return list;
}
getQueueLength操作

作用:获取队列中等待的线程数量
实现:从双向链表尾部开始查找,如果Node节点内部线程不为空,则计数器加1

public final int getQueueLength() {
   
   
    int n = 0;
    for (Node p = tail; p != null; p = p.prev) {
   
   
        if (p.thread != null)
            ++n;
    }
    return n;
}
getSharedQueuedThreads操作

作用:获取队列中共享(共享锁)结点等待的线程集合
实现:从双向链表尾部开始查找,如果Node节点是共享结点且内部线程不为空,则取出节点中存放的线程并将其加入列表

public final Collection<Thread> getSharedQueuedThreads() {
   
   
   ArrayList<Thread> list = new ArrayList<>();
    for (Node p = tail; p != null; p = p.prev) {
   
   
        if (p.isShared()) {
   
   
            Thread t = p.thread;
            if (t != null)
                list.add(t)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lolxxs

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值