💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
[TOC] 抽象队列同步器AbstractQueuedSynchronizer (以下都简称AQS),是用来构建锁或者其他同步组件的基础框架,它具备两个核心功能: * 使用了一个int成员变量来表示同步状态 * 通过内置的隐式FIFO同步队列(first-in-first-out)来控制获取共享资源的线程们 ## 核心方法 ### 独占式获取与释放同步状态 方法描述void acquire(int arg)独占式获取同步状态(该方法会调用子类重写的tryAcquire(int arg))。 如果当前线程获取同步状态成功,则返回以便继续执行,否则进入同步队列的尾部等待,该方法会调用tryAcquire(int arg)方法。void acquireInterruptibly(int arg)与 void acquire(int arg)基本逻辑相同,但是该方法响应中断。 如果当前没有获取到同步状态,那么就会进入等待队列,如果当前线程被中断(Thread().interrupt()),那么该方法将会抛出InterruptedException。并返回boolean tryAcquireNanos(int arg, long nanosTimeout)在acquireInterruptibly(int arg)的基础上,增加了超时限制,如果在超时时间内获取到同步状态返回true,否则返回falseboolean release(int arg)独占式释放同步状态,该方法会在释放同步状态后将第一个节点(对应刚刚释放同步状态的线程)的后继节点对应的线程唤醒。 ### 共享式获取与释放同步状态 方法描述void acquireShared(int arg)共享式的获取同步状态(该方法会调用子类重写的tryAcquireShared(int arg)). 如果当前线程未获取到同步状态,将会进入同步队列的尾部等待,与独占式获取的主要区别是在同一时刻可以有多个线程获取到同步状态。void acquireSharedInterruptibly(int arg)在acquireShared(int arg)的基本逻辑相同,增加了响应中断。boolean tryAcquireSharedNanos(int arg, long nanosTimeout)在acquireSharedInterruptibly的基础上,增加了超时限制。boolean releaseShared(int arg)共享式的释放同步状态 ## 同步队列 同步队列是AQS很重要的组成部分,它是一个双端队列,遵循FIFO原则,主要作用是用来存放在锁上阻塞的线程,当一个线程尝试获取锁时,如果已经被占用,那么当前线程就会被构造成一个Node节点假如到同步队列的尾部,队列的头节点是成功获取锁的节点,当头节点线程是否锁时,会唤醒后面的节点并释放当前头节点的引用。 //得到锁的在头部,得不到的在尾部。 ~~~ static final class Node { //等待状态 volatile int waitStatus; //当前转换为Node节点的线程。 volatile Thread thread; //当前节点在同步队列中的上一个节点。 volatile Node prev; //当前节点在同步队列中的下一个节点。 volatile Node next; //Node既可以作为同步队列节点(竞争使用共享资源的线程)使用,也可以作为Condition的等待队列节点(类似与调用Obect.wait等待线程)使用(将会在后面讲Condition时讲到)。 //在作为同步队列节点时,nextWaiter可能有两个值:EXCLUSIVE、SHARED标识当前节点是独占模式还是共享模式; //在作为等待队列节点使用时,nextWaiter保存后继节点。 Node nextWaiter; } ~~~ ## 等待状态主要包含以下状态 状态值含义CANCELLED1被中断或获取同步状态超时的线程将会被置为该状态,且该状态下的线程不会再阻塞。SIGNAL-1线程的后继线程正/已被阻塞,当该线程release或cancel时要重新这个后继线程(unpark)CONDITION-2标识当前节点是作为等待队列节点使用的。当前节点在Condition中的等待队列上,(关于Condition会在下篇文章进行介绍),其他线程调用了Condition的singal()方法后,该节点会从等待队列转移到AQS的同步队列中,等待获取同步锁。PROPAGATE-3与共享式获取同步状态有关,该状态标识的节点对应线程处于可运行的状态。00初始状态 其中, SIGNAL是个很重要的概念: 1. 当前节点的线程如果释放了或取消了同步状态,将会将当前节点的状态标志位SINGAL,用于通知当前节点的下一节点,准备获取同步状态。 2. 并且每个节点在阻塞前,需要标记其前驱节点的状态为SIGNAL。 ![](https://img.kancloud.cn/69/2f/692f0f65748aed951cbb683ff52a7bec_661x261.png) ~~~ public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable { private transient volatile Node head; private transient volatile Node tail; //保证了添加头节点 和 尾节点 加入的过程必须保证线程安全 private final boolean compareAndSetTail(Node expect, Node update) { return U.compareAndSwapObject(this, TAIL, expect, update); } } ~~~ ## 独占式同步状态获取 * **addWaiter构建节点并加入队尾** > 1\. 如果当前尾指针(tail)不为null,那么尝试将尾指针 tail 指向当前线程构造的Node节点,如果成功,那么将尾指针之前指向的节点的next指向当前线程构造的Node节点,并返回当前节点。 > 2\. 如果当前尾节点为空,即当前同步队列为空,则新建一个傀儡节点作为首节点和尾节点,然后再将当前节点设为尾节点。 * **acquireQueued 自旋式竞争或阻塞当前线程并重置pre关系** > 1\. 把已经追加到队列的线程节点(addWaiter方法返回值)进行阻塞,但阻塞前又通过tryAccquire重试是否能获得锁,如果重试成功能则无需阻塞,直接返回 > 2\. \*\* 再次被唤醒时,继续循环体,相当于 自旋式竞争锁\*\* * **cancelAcquire取消并移除当前节点和唤醒下一个节点** > 如果线程中断了,那么就将该线程从同步队列中移除,同时唤醒下一节点 **阻塞最终是调用 LockSupport.park(this);堵塞进程** ## 独占式同步状态释放 线程获取同步状态成功并执行相应逻辑后,需要释放同步状态,使得后继线程节点能够继续获取同步状态,通过调用AQS的relase(int arg)方法,可以释放同步状态。 boolean release(int arg)独占式释放同步状态,该方法会在释放同步状态后将第一个节点(对应刚刚释放同步状态的线程)的后继节点对应的线程唤醒。