写点什么

什么是 AQS 及源码分析

作者:Ayue、
  • 2021 年 12 月 25 日
  • 本文字数:20587 字

    阅读完需:约 68 分钟

概述

队列同步器 AbstractQueuedSynchronizer(以下简称 AQS),是用来构建锁或者其他同步组件的基础框架,它使用了一个 int 成员变量表示同步状 态,通过内置的 FIFO 队列来完成资源获取线程的排队工作。并发包的大师(DougLea)期望它能够成为实现大部分同步需求的基础。

AQS 使用方式和其中的设计模式

AQS 的主要使用方式是继承,子类通过继承 AQS 并实现它的抽象方法来管理同步状态,在 AQS 里由一个 int 型的 state 来代表这个状态,在抽象方法的实 现过程中免不了要对同步状态进行更改,这时就需要使用同步器提供的 3 个方法(getState()、setState(int newState)和 compareAndSetState(int expect,int update))来进行操作,因为它们能够保证状态的改变是安全的。


/** * 同步状态 */private volatile int state;
复制代码


在实现上,子类推荐被定义为自定义同步组件的静态内部类,AQS 自身没有实现任何同步接口,它仅仅是定义了若干同步状态获取和释放的方法来供自定义同步组件使用,同步器既可以支持独占式地获取同步状态,也可以支持共享式地获取同步状态,这样就可以方便实现不同类型的同步组件(ReentrantLock、ReentrantReadWriteLock 和 CountDownLatch 等)。同步器是实现锁(也可以是任意同步组件)的关键,在锁的实现中聚合同步器。可以这样理解二者之间的关系:


  • 锁是面向使用者的。它定义了使用者与锁交互的接口(比如可以允许两个线程并行访问),隐藏了实现细节

  • 同步器面向的是锁的实现者。它简化了锁的实现方式,屏蔽了同步状态管理、线程的排队、等待与唤醒等底层操作


锁和同步器很好地隔离了使用者和实现者所需关注的领域。实现者需要继承同步器并重写指定的方法,随后将同步器组合在自定义同步 组件的实现中,并调用同步器提供的模板方法,而这些模板方法将会调用使用者重写的方法。


同步器的设计基于模板方法模式。模板方法模式的意图是,定义一个操作中的算法的骨架,而将一些步骤的实现延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。我们最常见的就是 Spring 框架里的各种 Template。

AQS 中的方法

模板方法

实现自定义同步组件时,将会调用同步器提供的模板方法。



这些模板方法同步器提供的模板方法基本上分为 3 类:独占式获取与释放同步状态、共享式获取与释放、同步状态和查询同步队列中的等待线程情况。

可重写的方法

访问或修改同步状态的方法

重写同步器指定的方法时,需要使用同步器提供的如下 3 个方法来访问或修改同步状态。


  • getState():获取当前同步状态

  • setState(int newState):设置当前同步状态

  • compareAndSetState(int expect,int update):使用 CAS 设置当前状态,该方法能够保证状态设置的原子性

实现一个独占锁

代码如下


/** * 类说明:实现一个独占锁,不可重入 */public class MyLock implements Lock {
//静态内部内,自定义同步器 private static class Sync extends AbstractQueuedSynchronizer {
/** * 判断是否处于占用状态 */ @Override protected boolean isHeldExclusively() { return getState() == 1; }
/** * 获取锁 */ @Override protected boolean tryAcquire(int arg) { //为true代表当前线程抢到了锁 if (compareAndSetState(0, 1)) { //设置为独占锁 setExclusiveOwnerThread(Thread.currentThread()); return true; } return false; }
/** * 释放锁 */ @Override protected boolean tryRelease(int arg) { if (getState() == 0) { throw new IllegalMonitorStateException("当前线程未获取到锁"); } //把锁释放 setExclusiveOwnerThread(null); setState(0); return true; }

//返回一个Condition,每个Condition都包含了一个Condition队列 Condition newCondition() { return new ConditionObject(); } }
//把操作代理到Sync上 private final Sync sync = new Sync();
@Override public void lock() { System.out.println(Thread.currentThread().getName()+"---准备获取锁"); sync.acquire(1); System.out.println(Thread.currentThread().getName()+"---已经获取锁"); }
@Override public void lockInterruptibly() throws InterruptedException { sync.acquireInterruptibly(1); }
@Override public boolean tryLock() { return sync.tryAcquire(1); }
@Override public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { return sync.tryAcquireNanos(1,unit.toNanos(time)); }
@Override public void unlock() { System.out.println(Thread.currentThread().getName()+"---准备释放锁"); sync.release(1); System.out.println(Thread.currentThread().getName()+"---已经释放锁"); } public boolean isLocked() { return sync.isHeldExclusively(); }
public boolean hasQueuedThreads() { return sync.hasQueuedThreads(); } @Override public Condition newCondition() { return sync.newCondition(); }}
复制代码


测试代码


public class MyLockTest {
public void test() { final Lock lock = new MyLock(); class Worker extends Thread { public void run() { lock.lock(); System.out.println(Thread.currentThread().getName()); try { SleepTools.second(1); } finally { lock.unlock(); } } } // 启动4个子线程 for (int i = 0; i < 4; i++) { Worker w = new Worker(); w.start(); } // 主线程每隔1秒换行 for (int i = 0; i < 10; i++) { SleepTools.second(1); } }
public static void main(String[] args) { MyLockTest myLockTest = new MyLockTest(); myLockTest.test(); }}
复制代码


打印如下


Thread-1---准备获取锁Thread-0---准备获取锁Thread-3---准备获取锁Thread-2---准备获取锁Thread-1---已经获取锁Thread-1Thread-1---准备释放锁Thread-1---已经释放锁Thread-0---已经获取锁Thread-0Thread-0---准备释放锁Thread-0---已经释放锁Thread-3---已经获取锁Thread-3Thread-3---准备释放锁Thread-3---已经释放锁Thread-2---已经获取锁Thread-2Thread-2---准备释放锁Thread-2---已经释放锁
复制代码

AQS 数据结构

CLH 队列锁

CLH 队列锁即 Craig, Landin, and Hagersten (CLH) locks。CLH 队列锁也是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程仅仅在本地变量上自旋,它不断轮询前驱的状态,假设发现前驱释放了锁就结束自旋。


当一个线程需要获取锁时:


  1. 创建一个的 QNode,将其中的 locked 设置为 true 表示需要获取锁,myPred 表示对其前驱结点的引用



  1. 线程 A 对 tail 域调用 getAndSet 方法,使自己成为队列的尾部,同时获取一个指向其前驱结点的引用 myPred



线程 B 需要获得锁,同样的流程再来一遍



  1. 线程就在前驱结点的 locked 字段上旋转,直到前驱结点释放锁(前驱节点的锁值 locked == false)

  2. 当一个线程需要释放锁时,将当前结点的 locked 域设置为 false,同时回收前驱结点


如下图所示,前驱结点释放锁,线程 A 的 myPred 所指向的前驱结点的 locked 字段变为 false,线程 A 就可以获取到锁。CLH 队列锁的优点是空间复杂度低(如果有 n 个线程,L 个锁,每个线程每次只获取一个锁,那么需要的存储空间是 O(L+n),n 个线程有 n 个 myNode,L 个锁有 L 个 tail)。CLH 队列锁常用在 SMP 体系结构下。



Java 中的 AQS 是 CLH 队列锁的一种变体实现。

节点和同步队列

节点在同步队列中的增加和移出

节点加入到同步队列


当一个线程成功地获取了同步状态(或者锁),其他线程将无法获取到同步状态,也就是获取同步状态失败,AQS 会将这个线程以及等待状态等信息构造成为一个节点(Node)并将其加入同步队列的尾部。而这个加入队列的过程必须要保证线程安全,因此同步器提供了一个基于 CAS 的设置尾节点的方法:compareAndSetTail(Node expect,Nodeupdate),它需要传递当前线程认为的尾节点和当前节点,只有设置成功后,当前节点才正式与之前的尾节点建立关联。

首节点的变化


首节点是获取同步状态成功的节点,首节点的线程在释放同步状态时,将会唤醒后继节点,而后继节点将会在获取同步状态成功时将自己设置为首节点。设置首节点是通过获取同步状态成功的线程来完成的,由于只有一个线程能够成功获取到同步状态,因此设置头节点的方法并不需要使用 CAS 来保证,它只需要将首节点设置成为原首节点的后继节点并断开原首节点的 next 引用即可。

节点 Node

既然说 Java 中的 AQS 是 CLH 队列锁的一种变体实现,毫无疑问,作为队列来说,必然要有一个节点的数据结构来保存我们前面所说的各种域,比如前驱节点, 节点的状态等,这个数据结构就是 AQS 中的内部类 Node。作为这个数据结构应该关心些什么信息?


  1. 线程信息,肯定要知道我是哪个线程

  2. 队列中线程状态,既然知道是哪一个线程,肯定还要知道线程当前处在什么状态,是已经取消了“获锁”请求,还是在“等待中”,或者说“即将得到锁”

  3. 前驱和后继线程,因为是一个等待队列,那么也就需要知道当前线程前面的是哪个线程,当前线程后面的是哪个线程(因为当前线程释放锁以后,理当立马通知后继线程去获取锁)。


所以这个 Node 类的设计如下:



其中包括了:


  • 线程的 2 种等待模式

  • SHARED:表示线程以共享的模式等待锁(如 ReadLock)

  • EXCLUSIVE:表示线程以互斥的模式等待锁(ReetrantLock),互斥就是一把锁只能由一个线程持有,不能同时存在多个线程使用同一个锁

  • 线程在队列中的状态枚举

  • CANCELLED:值为 1,表示线程的获锁请求已经取消

  • SIGNAL:值为-1,表示该线程一切都准备好了,就等待锁空闲出来给我

  • CONDITION:值为-2,表示线程等待某一个条件(Condition)被满足

  • PROPAGATE:值为-3,当线程处在 SHARED 模式时,该字段才会被使用上。

  • 初始化 Node 对象时,waitStatus 默认为 0

  • 成员变量

  • waitStatus:该 int 变量表示线程在队列中的状态,其值就是上述提到的 CANCELLED、SIGNAL、CONDITION、PROPAGATE

  • prev:该变量类型为 Node 对象,表示该节点的前一个 Node 节点(前驱)

  • next:该变量类型为 Node 对象,表示该节点的后一个 Node 节点(后继)

  • thread:该变量类型为 Thread 对象,表示该节点的代表的线程

  • nextWaiter:该变量类型为 Node 对象,表示等待 condition 条件的 Node 节


当前线程获取同步状态失败时,同步器会将当前线程以及等待状态等信息构造成为一个节点(Node)并将其加入同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点中的线程唤醒,使其再次尝试获取同步状态。同步队列 中的节点(Node)用来保存获取同步状态失败的线程引用、等待状态以及前驱和后继节点。

head 和 tail

AQS 还拥有首节点(head)和尾节点(tail)两个引用,一个指向队列头节点,而另一个指向队列尾节点


注意:因为首节点 head 是不保存线程信息的节点,仅仅是因为数据结构设计上的需要,在数据结构上,这种做法往往叫做空头节点链表。对应的就有非空头结点链表

源码分析

类的继承关系

public abstract class AbstractQueuedSynchronizer    extends AbstractOwnableSynchronizer    implements java.io.Serializable 
复制代码


从类继承关系可知,AbstractQueuedSynchronizer 继承自 AbstractOwnableSynchronizer 抽象类,并且实现了 Serializable 接口,可以进行序列化。而 AbstractOwnableSynchronizer 抽象类的源码如下


public abstract class AbstractOwnableSynchronizer    implements java.io.Serializable {
/** Use serial ID even though all fields transient. */ private static final long serialVersionUID = 3737899427754241961L;
/** * 构造函数 */ protected AbstractOwnableSynchronizer() { }
/** * 独占模式下的线程 */ private transient Thread exclusiveOwnerThread;
/** * 设置独占线程 */ protected final void setExclusiveOwnerThread(Thread thread) { exclusiveOwnerThread = thread; }
/** * 获取独占线程 */ protected final Thread getExclusiveOwnerThread() { return exclusiveOwnerThread; }}
复制代码


AbstractOwnableSynchronizer 抽象类中,可以设置独占资源线程和获取独占资源线程。分别为 setExclusiveOwnerThread 与 getExclusiveOwnerThread 方法,这两个方法会被子类调用。

类的内部类

AbstractQueuedSynchronizer 类有两个内部类,分别为 Node 类与 ConditionObject 类。

Node 类

static final class Node {        /** 模式,分为共享与独占 */        /** 共享模式 */        static final Node SHARED = new Node();                /** 独占模式 */        static final Node EXCLUSIVE = null;                /** CANCELLED,值为1,表示当前的线程被取消 */        static final int CANCELLED =  1;                /** SIGNAL,值为-1,表示当前节点的后继节点包含的线程需要运行,也就是unpark */        static final int SIGNAL    = -1;                /** CONDITION,值为-2,表示当前节点在等待condition,也就是在condition队列中 */        static final int CONDITION = -2;                /**         * PROPAGATE,值为-3,表示当前场景下后续的acquireShared能够得以执行         */        static final int PROPAGATE = -3;
/** * 值为0,表示当前节点在sync队列中,等待着获取锁 */ /** 结点状态 */ volatile int waitStatus;
/** * 前驱节点 */ volatile Node prev;
/** * 后继结点 */ volatile Node next;
/** * 节点对应的线程 */ volatile Thread thread;
/** * 下一个等待者 */ Node nextWaiter;
/** * 结点是否在共享模式下等待 */ final boolean isShared() { return nextWaiter == SHARED; }
/** * 获取前驱结点,若前驱结点为空,抛出异常 */ final Node predecessor() throws NullPointerException { Node p = prev; if (p == null) throw new NullPointerException(); else return p; }
/** * 构造函数 */ Node() { // Used to establish initial head or SHARED marker } Node(Thread thread, Node mode) { // Used by addWaiter this.nextWaiter = mode; this.thread = thread; }
Node(Thread thread, int waitStatus) { // Used by Condition this.waitStatus = waitStatus; this.thread = thread; } }
复制代码


每个线程被阻塞的线程都会被封装成一个 Node 结点,放入队列。每个节点包含了一个 Thread 类型的引用,并且每个节点都存在一个状态,具体状态如下。

ConditionObject 类

public class ConditionObject implements Condition, java.io.Serializable {        // 版本号        private static final long serialVersionUID = 1173984872572414699L;                /** condition队列的头结点 */        private transient Node firstWaiter;                /** condition队列的尾结点 */        private transient Node lastWaiter;
/** * Creates a new {@code ConditionObject} instance. */ public ConditionObject() { }
// Internal methods
/** * 添加新的waiter到wait队列 */ private Node addConditionWaiter() { // 保存尾结点 Node t = lastWaiter; // If lastWaiter is cancelled, clean out. if (t != null && t.waitStatus != Node.CONDITION) { // 尾结点不为空,并且尾结点的状态不为CONDITION // 清除状态为CONDITION的结点 unlinkCancelledWaiters(); // 将最后一个结点重新赋值给t t = lastWaiter; } // 新建一个结点 Node node = new Node(Thread.currentThread(), Node.CONDITION); if (t == null) // 尾结点为空 // 设置condition队列的头结点 firstWaiter = node; else // 尾结点不为空 // 设置为节点的nextWaiter域为node结点 t.nextWaiter = node; // 更新condition队列的尾结点 lastWaiter = node; return node; }
private void doSignal(Node first) { // 循环 do { if ( (firstWaiter = first.nextWaiter) == null) // 该节点的nextWaiter为空 // 设置尾结点为空 lastWaiter = null; // 设置first结点的nextWaiter域 first.nextWaiter = null; } while (!transferForSignal(first) && (first = firstWaiter) != null); // 将结点从condition队列转移到sync队列失败并且condition队列中的头结点不为空,一直循环 }
/** * Removes and transfers all nodes. * @param first (non-null) the first node on condition queue */ private void doSignalAll(Node first) { // condition队列的头结点尾结点都设置为空 lastWaiter = firstWaiter = null; // 循环 do { // 获取first结点的nextWaiter域结点 Node next = first.nextWaiter; // 设置first结点的nextWaiter域为空 first.nextWaiter = null; // 将first结点从condition队列转移到sync队列 transferForSignal(first); // 重新设置first first = next; } while (first != null); }
// 从condition队列中清除状态为CANCEL的结点 private void unlinkCancelledWaiters() { // 保存condition队列头结点 Node t = firstWaiter; Node trail = null; while (t != null) { // t不为空 // 下一个结点 Node next = t.nextWaiter; if (t.waitStatus != Node.CONDITION) { // t结点的状态不为CONDTION状态 // 设置t节点的额nextWaiter域为空 t.nextWaiter = null; if (trail == null) // trail为空 // 重新设置condition队列的头结点 firstWaiter = next; else // trail不为空 // 设置trail结点的nextWaiter域为next结点 trail.nextWaiter = next; if (next == null) // next结点为空 // 设置condition队列的尾结点 lastWaiter = trail; } else // t结点的状态为CONDTION状态 // 设置trail结点 trail = t; // 设置t结点 t = next; } }

/** * 唤醒一个等待线程。如果所有的线程都在等待此条件,则选择其中的一个唤醒。在从 await 返 * 回之前,该线程必须重新获取锁 */ public final void signal() { if (!isHeldExclusively()) // 不被当前线程独占,抛出异常 throw new IllegalMonitorStateException(); // 保存condition队列头结点 Node first = firstWaiter; if (first != null) // 头结点不为空 // 唤醒一个等待线程 doSignal(first); }
/** * 唤醒所有等待线程。如果所有的线程都在等待此条件,则唤醒所有线程。在从 await 返回之 * 前,每个线程都必须重新获取锁 */ public final void signalAll() { if (!isHeldExclusively()) // 不被当前线程独占,抛出异常 throw new IllegalMonitorStateException(); // 保存condition队列头结点 Node first = firstWaiter; if (first != null) // 头结点不为空 // 唤醒所有等待线程 doSignalAll(first); }
/** * 等待,当前线程在接到信号之前一直处于等待状态,不响应中断 */ public final void awaitUninterruptibly() { // 添加一个结点到等待队列 Node node = addConditionWaiter(); // 获取释放的状态 int savedState = fullyRelease(node); boolean interrupted = false; while (!isOnSyncQueue(node)) { // // 阻塞当前线程 LockSupport.park(this); if (Thread.interrupted()) // 当前线程被中断 // 设置interrupted状态 interrupted = true; } if (acquireQueued(node, savedState) || interrupted) // selfInterrupt(); }

/** Mode meaning to reinterrupt on exit from wait */ private static final int REINTERRUPT = 1; /** Mode meaning to throw InterruptedException on exit from wait */ private static final int THROW_IE = -1;
/** * Checks for interrupt, returning THROW_IE if interrupted * before signalled, REINTERRUPT if after signalled, or * 0 if not interrupted. */ private int checkInterruptWhileWaiting(Node node) { return Thread.interrupted() ? (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) : 0; }
/** * Throws InterruptedException, reinterrupts current thread, or * does nothing, depending on mode. */ private void reportInterruptAfterWait(int interruptMode) throws InterruptedException { if (interruptMode == THROW_IE) throw new InterruptedException(); else if (interruptMode == REINTERRUPT) selfInterrupt(); }
/** * 等待,当前线程在接到信号或被中断之前一直处于等待状态 */ public final void await() throws InterruptedException { if (Thread.interrupted()) // 当前线程被中断,抛出异常 throw new InterruptedException(); // 在wait队列上添加一个结点 Node node = addConditionWaiter(); // int savedState = fullyRelease(node); int interruptMode = 0; while (!isOnSyncQueue(node)) { // 阻塞当前线程 LockSupport.park(this); if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) // 检查结点等待时的中断类型 break; } if (acquireQueued(node, savedState) && interruptMode != THROW_IE) interruptMode = REINTERRUPT; if (node.nextWaiter != null) // clean up if cancelled unlinkCancelledWaiters(); if (interruptMode != 0) reportInterruptAfterWait(interruptMode); }
/** * 等待,当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态 */ public final long awaitNanos(long nanosTimeout) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); Node node = addConditionWaiter(); int savedState = fullyRelease(node); final long deadline = System.nanoTime() + nanosTimeout; int interruptMode = 0; while (!isOnSyncQueue(node)) { if (nanosTimeout <= 0L) { transferAfterCancelledWait(node); break; } if (nanosTimeout >= spinForTimeoutThreshold) LockSupport.parkNanos(this, nanosTimeout); if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break; nanosTimeout = deadline - System.nanoTime(); } if (acquireQueued(node, savedState) && interruptMode != THROW_IE) interruptMode = REINTERRUPT; if (node.nextWaiter != null) unlinkCancelledWaiters(); if (interruptMode != 0) reportInterruptAfterWait(interruptMode); return deadline - System.nanoTime(); }
/** * 等待,当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态 */ public final boolean awaitUntil(Date deadline) throws InterruptedException { long abstime = deadline.getTime(); if (Thread.interrupted()) throw new InterruptedException(); Node node = addConditionWaiter(); int savedState = fullyRelease(node); boolean timedout = false; int interruptMode = 0; while (!isOnSyncQueue(node)) { if (System.currentTimeMillis() > abstime) { timedout = transferAfterCancelledWait(node); break; } LockSupport.parkUntil(this, abstime); if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break; } if (acquireQueued(node, savedState) && interruptMode != THROW_IE) interruptMode = REINTERRUPT; if (node.nextWaiter != null) unlinkCancelledWaiters(); if (interruptMode != 0) reportInterruptAfterWait(interruptMode); return !timedout; }
/** * 等待,当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。此方法在行为 * 上等效于:awaitNanos(unit.toNanos(time)) > 0 */ public final boolean await(long time, TimeUnit unit) throws InterruptedException { long nanosTimeout = unit.toNanos(time); if (Thread.interrupted()) throw new InterruptedException(); Node node = addConditionWaiter(); int savedState = fullyRelease(node); final long deadline = System.nanoTime() + nanosTimeout; boolean timedout = false; int interruptMode = 0; while (!isOnSyncQueue(node)) { if (nanosTimeout <= 0L) { timedout = transferAfterCancelledWait(node); break; } if (nanosTimeout >= spinForTimeoutThreshold) LockSupport.parkNanos(this, nanosTimeout); if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break; nanosTimeout = deadline - System.nanoTime(); } if (acquireQueued(node, savedState) && interruptMode != THROW_IE) interruptMode = REINTERRUPT; if (node.nextWaiter != null) unlinkCancelledWaiters(); if (interruptMode != 0) reportInterruptAfterWait(interruptMode); return !timedout; }

/** * Returns true if this condition was created by the given * synchronization object. * * @return {@code true} if owned */ final boolean isOwnedBy(AbstractQueuedSynchronizer sync) { return sync == AbstractQueuedSynchronizer.this; }
/** * 查询是否有正在等待此条件的任何线程 */ protected final boolean hasWaiters() { if (!isHeldExclusively()) throw new IllegalMonitorStateException(); for (Node w = firstWaiter; w != null; w = w.nextWaiter) { if (w.waitStatus == Node.CONDITION) return true; } return false; }
/** * 返回正在等待此条件的线程数估计值 */ protected final int getWaitQueueLength() { if (!isHeldExclusively()) throw new IllegalMonitorStateException(); int n = 0; for (Node w = firstWaiter; w != null; w = w.nextWaiter) { if (w.waitStatus == Node.CONDITION) ++n; } return n; }
/** * 返回包含那些可能正在等待此条件的线程集合 */ protected final Collection<Thread> getWaitingThreads() { if (!isHeldExclusively()) throw new IllegalMonitorStateException(); ArrayList<Thread> list = new ArrayList<Thread>(); for (Node w = firstWaiter; w != null; w = w.nextWaiter) { if (w.waitStatus == Node.CONDITION) { Thread t = w.thread; if (t != null) list.add(t); } } return list; }}
复制代码


此类实现了 Condition 接口,Condition 接口定义了条件操作规范,具体如下


public interface Condition {
// 等待,当前线程在接到信号或被中断之前一直处于等待状态 void await() throws InterruptedException; // 等待,当前线程在接到信号之前一直处于等待状态,不响应中断 void awaitUninterruptibly(); //等待,当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态 long awaitNanos(long nanosTimeout) throws InterruptedException; // 等待,当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。此方法在行为上等效于:awaitNanos(unit.toNanos(time)) > 0 boolean await(long time, TimeUnit unit) throws InterruptedException; // 等待,当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态 boolean awaitUntil(Date deadline) throws InterruptedException; // 唤醒一个等待线程。如果所有的线程都在等待此条件,则选择其中的一个唤醒。在从 await 返回之前,该线程必须重新获取锁。 void signal(); // 唤醒所有等待线程。如果所有的线程都在等待此条件,则唤醒所有线程。在从 await 返回之前,每个线程都必须重新获取锁。 void signalAll();}
复制代码


Condition 接口中定义了 await、signal 函数,用来等待条件、释放条件。

类的属性

public abstract class AbstractQueuedSynchronizer    extends AbstractOwnableSynchronizer    implements java.io.Serializable {        // 版本号    private static final long serialVersionUID = 7373984972572414691L;        // 头结点    private transient volatile Node head;        // 尾结点    private transient volatile Node tail;        // 状态    private volatile int state;        // 自旋时间    static final long spinForTimeoutThreshold = 1000L;        // Unsafe类实例    private static final Unsafe unsafe = Unsafe.getUnsafe();    // state内存偏移地址    private static final long stateOffset;    // head内存偏移地址    private static final long headOffset;    // state内存偏移地址    private static final long tailOffset;    // tail内存偏移地址    private static final long waitStatusOffset;    // next内存偏移地址    private static final long nextOffset;    // 静态初始化块    static {        try {            stateOffset = unsafe.objectFieldOffset                (AbstractQueuedSynchronizer.class.getDeclaredField("state"));            headOffset = unsafe.objectFieldOffset                (AbstractQueuedSynchronizer.class.getDeclaredField("head"));            tailOffset = unsafe.objectFieldOffset                (AbstractQueuedSynchronizer.class.getDeclaredField("tail"));            waitStatusOffset = unsafe.objectFieldOffset                (Node.class.getDeclaredField("waitStatus"));            nextOffset = unsafe.objectFieldOffset                (Node.class.getDeclaredField("next"));
} catch (Exception ex) { throw new Error(ex); } }}
复制代码


属性中包含了头结点 head,尾结点 tail,状态 state、自旋时间 spinForTimeoutThreshold,还有 AbstractQueuedSynchronizer 抽象的属性在内存中的偏移地址,通过该偏移地址,可以获取和设置该属性的值,同时还包括一个静态初始化块,用于加载内存偏移地址。

类的核心函数

独占式同步状态获取与释放

获取

acquire 函数


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


通过调用同步器的 acquire(int arg)方法可以获取同步状态,主要完成了同步状态获取、节点构造、加入同步队列以及在同步队列中自旋等待的相关工作。


  1. 首先调用 tryAcquire 函数,该方法需要保证线程安全的获取同步状态。

  2. 如果同步状态获取失败(tryAcquire 返回 false),则构造同步节点(独占式 Node.EXCLUSIVE,同一时刻只能有一个线程成功获取同步状态)并通过 addWaiter(Node node)方法将该节点加入到同步队列的尾部。

  3. 最后调用 acquireQueued(Node node,int arg)方法,使得该节点以死循环的方式获取同步状态。如果获取不到则阻塞节点中的线程,而被阻塞线程的唤醒主要依靠前驱节点的出队或阻塞线程被中断来实现。


addWaiter(Node node)


private Node addWaiter(Node mode) {		//新生成一个节点,默认为独占模式        Node node = new Node(Thread.currentThread(), mode);        //保存尾节点        Node pred = tail;        //尾节点不为空,即已经被初始化        if (pred != null) {        	//将node节点的prev域链接到尾节点            node.prev = pred;            //比较pred是否为尾结点,是则将尾结点设置为node             if (compareAndSetTail(pred, node)) {            	//设置尾结点的next域为node                pred.next = node;                return node; //返回新生成的结点            }        }        //尾结点为空(即还没有被初始化过),或者是compareAndSetTail操作失败,则入队列        enq(node);        return node;}
复制代码


将当前线程包装成 Node 后,队列不为空的情况下,先尝试快速添加的方式把当前节点加入队列并成为尾节点,如果不成功或者队列为空进入 enq(final Node node)方法


enq(final Node node)


private Node enq(final Node node) {		//无限循环,确保结点能够成功入队列        for (;;) {        	//保存尾节点            Node t = tail;            //尾结点为空,说明还没有被初始化            if (t == null) { // Must initialize            	//头结点为空,并设置头结点为新生成的结点                if (compareAndSetHead(new Node()))                	//头结点与尾结点都指向同一个新生结点                    tail = head;            } else {//尾结点不为空,即已经被初始化过            	//将node结点的prev域连接到尾结点                node.prev = t;                //比较结点t是否为尾结点,若是则将尾结点设置为node                if (compareAndSetTail(t, node)) {                	//设置尾结点的next域为node                    t.next = node;                    return t;                }            }        }    }
复制代码


在 enq(final Node node)方法中,同步器通过死循环来保证节点的正确添加,这个死循环中,做了两件事:


  • 第一件,如果队列为空,初始化队列,new 出一个空节点,并让首节点(head)和尾节点(tail)两个引用都指向这个空节点


  • 第二件,把当前节点加入队列。


在死循环中只有通过 CAS 将节点设置成为尾节点之后,当前线程才能从该方法返回,否则,当前线程不断地尝试设置。


节点进入同步队列之后,观察 acquireQueued(Node node,int arg)方法


acquireQueued(Node node,int arg)


	//sync队列中的结点在独占且忽略中断的模式下获取(资源)    final boolean acquireQueued(final Node node, int arg) {        //标志        boolean failed = true;        try {            //中断标志            boolean interrupted = false;            for (;;) { //无限循环                // 获取node节点的前驱结点                final Node p = node.predecessor();                 if (p == head && tryAcquire(arg)) { //前驱为头结点并且成功获得锁                    setHead(node); //设置头结点                    p.next = null; // help GC  删除原有头节点                    failed = false; //设置标志                    return interrupted;                 }                //线程进入阻塞状态,等待被唤醒(锁被释放的时候唤醒)                if (shouldParkAfterFailedAcquire(p, node) &&                    parkAndCheckInterrupt())                    interrupted = true;            }        } finally {            if (failed)                cancelAcquire(node);        }    }
复制代码


首先获取当前节点的前驱节点,如果前驱节点是头结点并且能够获取(资源),代表该当前节点能够占有锁,设置头结点为当前节点,返回。否则,调用 shouldParkAfterFailedAcquire 和 parkAndCheckInterrupt 函数,首先,我们看 shouldParkAfterFailedAcquire 函数(字面意思挺好理解,在获取资源失败之后是否需要阻塞),代码如下


  // 当获取(资源)失败后,检查并且更新结点状态    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {        // 获取前驱结点的状态        int ws = pred.waitStatus;        if (ws == Node.SIGNAL) // 状态为SIGNAL,为-1            // 可以进行park操作            return true;         if (ws > 0) { // 表示状态为CANCELLED,为1            do {                node.prev = pred = pred.prev;            } while (pred.waitStatus > 0); // 找到pred结点前面最近的一个状态不为CANCELLED的结点            // 赋值pred结点的next域            pred.next = node;         } else { // 为PROPAGATE -3 或者是0 表示无状态,(为CONDITION -2时,表示此节点在condition queue中)             // 比较并设置前驱结点的状态为SIGNAL            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);         }        // 不能进行park操作        return false;    }
复制代码


只有当该节点的前驱结点的状态为 SIGNAL 时,才可以对该结点所封装的线程进行 park 操作。否则,将不能进行 park 操作。再看 parkAndCheckInterrupt 函数,源码如下


	// 当获取(资源)失败后,检查并且更新结点状态    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {        // 获取前驱结点的状态        int ws = pred.waitStatus;        if (ws == Node.SIGNAL) // 状态为SIGNAL,为-1            // 可以进行park操作            return true;         if (ws > 0) { // 表示状态为CANCELLED,为1            do {                node.prev = pred = pred.prev;            } while (pred.waitStatus > 0); // 找到pred结点前面最近的一个状态不为CANCELLED的结点            // 赋值pred结点的next域            pred.next = node;         } else { // 为PROPAGATE -3 或者是0 表示无状态,(为CONDITION -2时,表示此节点在condition queue中)             // 比较并设置前驱结点的状态为SIGNAL            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);         }        // 不能进行park操作        return false;    }
复制代码


parkAndCheckInterrupt 函数里的逻辑是首先执行 park 操作,即禁用当前线程,然后返回该线程是否已经被中断。再看 final 块中的 cancelAcquire 函数,其源码如下


	//进行park操作并且返回该线程是否被中断    private final boolean parkAndCheckInterrupt() {        //在许可可用之前禁用当前线程,并且设置了blocker        LockSupport.park(this);        //当前线程是否已被中断,并清除中断标记位        return Thread.interrupted();     }
复制代码


该函数完成的功能就是取消当前线程对资源的获取,即设置该结点的状态为 CANCELLED,接着我们再看 unparkSuccessor 函数,源码如下


	//取消继续获取(资源)    private void cancelAcquire(Node node) {        // node为空,返回        if (node == null)            return;        // 设置node结点的thread为空        node.thread = null;        // 保存node的前驱结点        Node pred = node.prev;        //找到node前驱结点中第一个状态小于0的结点,即不为CANCELLED状态的结点        while (pred.waitStatus > 0)             node.prev = pred = pred.prev;        //获取pred结点的下一个结点        Node predNext = pred.next;        //设置node结点的状态为CANCELLED        node.waitStatus = Node.CANCELLED;
//node结点为尾结点,则设置尾结点为pred结点 if (node == tail && compareAndSetTail(node, pred)) { // 比较并设置pred结点的next节点为null compareAndSetNext(pred, predNext, null); } else { // node结点不为尾结点,或者比较设置不成功 int ws; if (pred != head && ((ws = pred.waitStatus) == Node.SIGNAL || (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) && pred.thread != null) { // (pred结点不为头结点,并且pred结点的状态为SIGNAL)或者 // pred结点状态小于等于0,并且比较并设置等待状态为SIGNAL成功,并且pred结点所封装的线程不为空 // 保存结点的后继 Node next = node.next; if (next != null && next.waitStatus <= 0) // 后继不为空并且后继的状态小于等于0 compareAndSetNext(pred, predNext, next); // 比较并设置pred.next = next; } else { unparkSuccessor(node); // 释放node的前一个结点 }
node.next = node; // help GC } }
复制代码


该函数的作用就是为了唤醒 node 节点的后继结点。也就是当前节点拿到锁释放之后,回去唤醒后继结点,所以此时把首节点指向了释放锁后的下一节点。


在 acquireQueued(final Node node,int arg)方法中,当前线程在死循环中尝试获取同步状态,而只有前驱节点是头节点才能够尝试获取同步状态,这是为什么?原因有两个


  • 第一,头节点是成功获取到同步状态的节点,而头结点的线程释放了同步状态之后,将会唤醒其后继节点,而后继节点的线程被唤醒后需要检查自己的前驱节点是否是头节点。

  • 第二,维护同步队列的 FIFO 原则。当线程获取到同步状态后,让首节点这个引用指向自己所在节点。当同步状态获取成功后,当前线程就从 acquire 方法返回了。如果同步器实现的是锁,那就代表当前线程获得了锁。


独占式锁的获取过程也就是 acquire()方法的执行流程


释放

当前线程获取同步状态并执行了相应逻辑之后,就需要释放同步状态,使得后续节点能够继续获取同步状态。通过调用同步器的 release(int arg)方法可以释放同步状态,该方法在释放了同步状态之后,会唤醒其后继节点(进而使后继节点重新尝试获取同步状态)。


public final boolean release(int arg) {        if (tryRelease(arg)) { // 释放成功            // 保存头结点            Node h = head;             if (h != null && h.waitStatus != 0) // 头结点不为空并且头结点状态不为0                unparkSuccessor(h); //释放头结点的后继结点            return true;        }        return false;    }
复制代码


该方法执行时,会唤醒首节点(head)所指向节点的后继节点线程,unparkSuccessor(Node node)方法使用 LockSupport(上面已经讲到) 来唤醒处于等待状态的线程。

总结

在获取同步状态时,同步器维护一个同步队列,获取状态失败的线程都会被加入到队列中并在队列中进行自旋;移出队列(或停止自旋)的条件是前驱节点为头节点且成功获取了同步状态。在释放同步状态时,同步器调用 tryRelease(int arg)方法释放同步状态,然后唤醒 head 指向节点的后继节点。

共享式同步状态获取与释放

共享式获取与独占式获取最主要的区别在于同一时刻能否有多个线程同时获取到同步状态。以读写为例,如果一个程序在进行读操作,那么这一时刻写操作均被阻塞,而读操作能够同时进行。写操作要求对资源的独占式访问,而读操作可以是共享式访问。


在 acquireShared(int arg)方法中,同步器调用 tryAcquireShared(int arg)方法尝试获取同步状态,tryAcquireShared(int arg)方法返回值为 int 类型,当返回值大于等于 0 时,表示能够获取到同步状态。


因此,在共享式获取的自旋过程中,成功获取到同步状态并退出自旋的条件就是 tryAcquireShared(int arg)方法返回值大于等于 0。可以看到,在 doAcquireShared(int arg)方法的自旋过程中,如果当前节点的前驱为头节点时,尝试获取同步状态,如果返回值大于等于 0,表示该次获取同步状态成功并从自旋过程中退出。该方法在释放同步状态之后,将会唤醒后续处于等待状态的节点。


对于能够支持多个线程同时访问的并发组件(比如 Semaphore),它和独占式主要区别在于 tryReleaseShared(int arg)方法必须确保同步状态(或者资源数)线程安全释放,一般是通过循环和 CAS 来保证的,因为释放同步状态的操作会同时来自多个线程。


这里对共享式同步状态获取与释放做过多讲解,本质和独占式区别不大。

总结

AQS 的核心思想是,如果请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并将共享资源设置为锁定状态,如果被请求的共享资源呗占用,那么就需要一套线程阻塞等待以及被唤醒的锁分配机制,这个机制 AQS 是用 CLH 队列锁实现的,即将暂时获取不到锁的线程加入到队列中。


AQS 是将每一条请求共享资源的线程封装成一个 CLH 锁队列的一个结点(Node),来实现锁的分配。


即 AQS 就是基于 CLH 队列,用 volatile 修饰共享变量 state,线程通过 CAS 去改变状态符,成功则获取锁成功,失败则进入等待队列,等待被唤醒。

发布于: 2 小时前
用户头像

Ayue、

关注

个人站点:javatv.net 2019.10.16 加入

学习知识,目光坚毅

评论

发布
暂无评论
什么是 AQS 及源码分析