Java 多线程 - AQS 队列同步器(Abstract Queued Synchronizer)
总结
1. 什么是AQS ?
AQS的全称是AbstractQueuedSynchronizer,它是为Java中几乎所有的锁和同步器提供一个基础框架, 拥有一个同步队列和多个等待队列:
1.1 AQS vs 锁
锁和同步器很好地隔离了使用者和实现者 所需关注的领域:
- 锁是面向使用者的,它定义了使用者与锁交互的接口(比如可以允许两个线 程并行访问),隐藏了实现细节;
- 同步器面向的是锁的实现者,它简化了锁的实现方式,屏蔽了同步状态管理、 线程的排队、等待与唤醒等底层操作。锁和同步器很好地隔离了使用者和实现者 所需关注的领域。
如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。
如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
AQS使用一个voliate int成员变量state,来表示同步状态,通过内置的FIFO队列来完成获取资源线程的排队工作
AQS使用CAS对该同步状态进行原子操作实现对state其值的修改。
2. 状态变量 state
AQS中定义了一个状态变量state,它有以下两种使用方法:
2.1 互斥锁
当AQS只实现为互斥锁的时候,每次只要原子更新state的值从0变为1成功了就获取了锁,可重入是通过不断把state原子更新加1实现的。
2.2 互斥锁 + 共享锁
当AQS需要同时实现为互斥锁+共享锁的时候,低16位存储互斥锁的状态,高16位存储共享锁的状态,主要用于实现读写锁。
互斥锁是一种独占锁,每次只允许一个线程独占,且当一个线程独占时,其它线程将无法再获取互斥锁及共享锁,但是它自己可以获取共享锁。
共享锁同时允许多个线程占有,只要有一个线程占有了共享锁,所有线程(包括自己)都将无法再获取互斥锁,但是可以获取共享锁。
3. AQS队列
AQS中维护了一个队列,获取锁失败(非tryLock())的线程都将进入这个队列中排队,等待锁释放后唤醒下一个排队的线程(互斥锁模式下)。
4. Condition队列
AQS中还有另一个非常重要的内部类ConditionObject,它实现了Condition接口,主要用于实现条件锁。
当前线程调用 Condition.await()方法,将会以当前线程构造节点,并将节点从尾部加入等待队列。上述节点引用更新的过程并没有使用 CAS 保证,原因在于调用 await()方法的线程必定是 获取了锁的线程,也就是说该过程是由锁来保证线程安全的。
当前线程调用 Condition.signal()方法,将会唤醒在等待队列中等待时间最长的节点 (首节点),在唤醒节点之前,会将节点移到同步队列中。
5. AQS 模板方法
模板方法同步器提供的模板方法基本上分为 3 类: 独占式获取与释放同步状态、共享式获取与释放、同步状态和查询同步队列中的等待线程情况。
// 获取互斥锁 public final void acquire(int arg) { // tryAcquire(arg)需要子类实现 if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } // 获取互斥锁可中断 public final void acquireInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); // tryAcquire(arg)需要子类实现 if (!tryAcquire(arg)) doAcquireInterruptibly(arg); } // 获取共享锁 public final void acquireShared(int arg) { // tryAcquireShared(arg)需要子类实现 if (tryAcquireShared(arg) < 0) doAcquireShared(arg); } // 获取共享锁可中断 public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); // tryAcquireShared(arg)需要子类实现 if (tryAcquireShared(arg) < 0) doAcquireSharedInterruptibly(arg); } // 释放互斥锁 public final boolean release(int arg) { // tryRelease(arg)需要子类实现 if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; } // 释放共享锁 public final boolean releaseShared(int arg) { // tryReleaseShared(arg)需要子类实现 if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false;
5.1 需要子类实现的方法
// 互斥模式下使用:尝试获取锁 protected boolean tryAcquire(int arg) { throw new UnsupportedOperationException(); } // 互斥模式下使用:尝试释放锁 protected boolean tryRelease(int arg) { throw new UnsupportedOperationException(); } // 共享模式下使用:尝试获取锁 protected int tryAcquireShared(int arg) { throw new UnsupportedOperationException(); } // 共享模式下使用:尝试释放锁 protected boolean tryReleaseShared(int arg) { throw new UnsupportedOperationException(); } // 如果当前线程独占着锁,返回true protected boolean isHeldExclusively() { throw new UnsupportedOperationException(); }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?