深入理解 JUC:AQS 队列同步器

AbstractQueuedSynchronizer 简称 AQS , 可能我们几乎不会直接去使用它 , 但它却是 JUC 的核心基础组件 , 支撑着 java 锁和同步器的实现 , 例如 ReentrantLock、ReentrantReadWriteLock、CountDownLatch , 以及 Semaphore 等 。 大神 Doug Lea 在设计 JUC 包时希望能够抽象一个基础且通用的组件以支撑上层模块的实现 , AQS 应运而生 。
AQS 本质上是一个 FIFO 的双向队列 , 线程被包装成结点的形式 , 基于自旋机制在队列中等待获取资源(这里的资源可以简单理解为对象锁) 。 AQS 在设计上实现了两类队列 , 即 同步队列 和 条件队列, 其中同步队列服务于线程阻塞等待获取资源 , 而条件队列则服务于线程因某个条件不满足而进入等待状态 。 条件队列中的线程实际上已经获取到了资源 , 但是没有能够继续执行下去的条件 , 所以被打入条件队列并释放持有的资源 , 以让渡其它线程执行 , 如果未来某个时刻条件得以满足 , 则该线程会被从条件队列转移到同步队列 , 继续参与竞争资源 , 以继续向下执行 。
本文我们主要分析 AQS 的设计与实现 , 包括 LockSupport 工具类、同步队列、条件队列 , 以及 AQS 资源获取和释放的通用过程 。 AQS 采用模板方法设计模式 , 具体获取资源和释放资源的过程都交由子类实现 , 对于这些方法的分析将留到后面分析具体子类的文章中再展开 。
LockSupport 工具类LockSupport 工具类是 JUC 的基础组件 , 主要作用是用来阻塞和唤醒线程 , 底层依赖于 Unsafe 类实现 。 LockSupport 主要定义类 2 类方法:park 和 unpark , 其中 park 方法用于阻塞当前线程 , 而 unpark 方法用于唤醒处于阻塞状态的指定线程 。
下面的示例演示了 park 和 unpark 方法的基本使用:
Thread thread = new Thread(() -> {System.out.println("Thread start: " + Thread.currentThread().getName());LockSupport.park(); // 阻塞自己System.out.println("Thread end: " + Thread.currentThread().getName());});thread.setName("A");thread.start();System.out.println("Main thread sleep 3 second: " + Thread.currentThread().getId());TimeUnit.SECONDS.sleep(3);LockSupport.unpark(thread); // 唤醒线程 A线程 A 在启动之后调用了 LockSupport#park 方法将自己阻塞 , 主线程在休息 3 秒之后调用 LockSupport#unpark 方法线程 A 唤醒 。 运行结果:
Thread start: AMain thread sleep 3 second: 1Thread end: ALockSupport 针对 park 方法提供了多种实现 , 如下:
public static void park()public static void park(Object blocker)public static void parkNanos(long nanos)public static void parkNanos(Object blocker, long nanos)public static void parkUntil(long deadline)public static void parkUntil(Object blocker, long deadline)由方法命名不难看出 , parkNanos 和 parkUntil 都属于 park 方法的超时版本 , 区别在于 parkNanos 方法接收一个纳秒单位的时间值 , 用于指定阻塞的时间长度 , 例如当设置 nanos=3000000000 时 , 线程将阻塞 3 秒后苏醒 , 而 parkUntil 方法则接收一个时间戳 , 参数 deadline 用于指定阻塞的到期时间 。
所有的 park 方法都提供了包含 Object blocker 参数的重载版本 , 参数 blocker 指代导致当前线程阻塞等待的锁对象 , 方便问题排查和系统监控 , 而在 LockSupport 最开始被设计时却忽视了这一点 , 导致在线程 dump 时无法提供阻塞对象的相关信息 , 这一点在 java 6 中得以改进 。 实际开发中如果使用到了 LockSupport 工具类 , 推荐使用带 blocker 参数的版本 。
下面以 LockSupport#park(java.lang.Object) 方法为例来看一下具体的实现 , 如下:


推荐阅读