深入理解 Java AQS 原理与 ReentrantLock 实现
目录
- 一、AQS 简介
- 二、AQS 核心设计
- 三、ReentrantLock 与 AQS 的关系
- 四、AQS 关键流程分析
- 五、公平锁与非公平锁
- 六、自定义实现:简化版 ReentrantLock
- 七、Condition 实现原理
- 八、AQS 的应用场景
- 九、总结
一、AQS 简介
AbstractQueuedSynchronizer(简称 AQS)是 Java 并发包(java.util.concurrent)中最核心的基础组件之一,它为 Java 中的大多数同步类(如 ReentrantLock、Semaphore、CountDownLatch 等)提供了一个通用的框架。理解 AQS 的工作原理对于深入掌握 Java 并发编程至关重要。
AQS 的作用是解决同步器的实现问题,它将复杂的同步器实现分解为简单的框架方法,开发者只需要实现少量特定的方法就能快速构建出可靠的同步器。
二、AQS 核心设计
2.1 核心组成部分
AQS 主要由以下部分组成:
- 同步状态(state):使用 volatile int 类型的变量表示资源的可用状态
- FIFO 等待队列:使用双向链表实现的队列,用于管理等待获取资源的线程
- 独占/共享模式:支持独占锁(如 ReentrantLock)和共享锁(如 CountDownLatch)两种模式
- 条件变量:通过 ConditionObject 类提供条件等待/通知机制,类似于 Object.wait()/notify()
2.2 AQS 的工作原理
AQS 通过模板方法模式,将一些通用的同步操作封装在框架内部,而将特定同步器的特性(如资源是否可获取的判断)交给子类去实现。AQS 提供以下基本操作:
- 资源获取:线程尝试获取资源,如果获取不到,将被包装成 Node 加入等待队列并被阻塞
- 资源释放:持有资源的线程释放资源后,会唤醒等待队列中的下一个线程
- 线程阻塞与唤醒:通过 LockSupport 的 park/unpark 机制实现
2.3 AQS 的关键方法
AQS 定义了一组需要子类实现的方法:
- tryAcquire(int):尝试以独占模式获取资源
- tryRelease(int):尝试以独占模式释放资源
- tryAcquireShared(int):尝试以共享模式获取资源
- tryReleaseShared(int):尝试以共享模式释放资源
- isHeldExclusively():判断资源是否被当前线程独占
三、ReentrantLock 与 AQS 的关系
ReentrantLock 是基于 AQS 实现的可重入锁,它通过内部类 Sync(继承自 AQS)来实现锁的基本功能,并通过 FairSync 和 NonfairSync 两个子类分别实现公平锁和非公平锁。
3.1 ReentrantLock 的结构
public class ReentrantLock implements Lock {
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {
// 实现锁的基本操作
}
// 公平锁实现
static final class FairSync extends Sync { ... }
// 非公平锁实现
static final class NonfairSync extends Sync { ... }
}
3.2 ReentrantLock 如何使用 AQS 的 state
ReentrantLock 使用 AQS 的 state 字段来表示锁的持有次数:
- state = 0:表示锁未被持有
- state > 0:表示锁被持有,值表示重入次数
四、AQS 关键流程分析
4.1 独占锁的获取流程
当线程调用 ReentrantLock.lock()方法时,实际上会执行以下流程:
- 首先调用 AQS 的 acquire(1)方法:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire 尝试获取锁,这是由 ReentrantLock 的 Sync 子类实现的:
- 如果 state=0,尝试使用 CAS 将 state 设为 1,并设置当前线程为持有锁的线程
- 如果当前线程已经持有锁,则增加 state 值,实现可重入
- 其他情况下返回 false
如果 tryAcquire 失败,则调用 addWaiter 将当前线程封装成 Node 添加到等待队列末尾:
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// 尝试快速添加到队列尾部
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 快速添加失败,进入完整的入队方法
enq(node);
return node;
}
- 然后执行 acquireQueued 方法,让该节点在队列中不断尝试获取锁,直到成功或被中断:
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
// 获取前驱节点
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);
}
}
4.2 独占锁的释放流程
当线程调用 ReentrantLock.unlock()方法时,会执行以下流程:
- 首先调用 AQS 的 release(1)方法:
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
tryRelease 尝试释放锁,这是由 ReentrantLock 的 Sync 类实现的:
- 检查当前线程是否是持有锁的线程
- 减少 state 值
- 如果 state 变为 0,清空持有锁的线程,并返回 true
如果 tryRelease 返回 true,表示已完全释放锁,则调用 unparkSuccessor 唤醒等待队列中的下一个线程:
private void unparkSuccessor(Node node) {
// 获取当前节点的等待状态
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
// 找到下一个需要唤醒的节点
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
// 从尾部向前查找需要唤醒的节点
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
// 唤醒找到的节点
if (s != null)
LockSupport.unpark(s.thread);
}
五、公平锁与非公平锁
ReentrantLock 支持公平锁和非公平锁两种模式:
5.1 非公平锁(默认)
非公平锁的 tryAcquire 实现:
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 非公平锁直接尝试CAS获取锁,不检查队列
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
5.2 公平锁
公平锁的 tryAcquire 实现:
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 公平锁会先调用hasQueuedPredecessors检查是否有前驱节点在等待
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
公平锁与非公平锁的主要区别在于获取锁时是否考虑等待队列。公平锁会检查是否有线程在等待队列中排队,而非公平锁则直接尝试获取,不考虑等待顺序。
六、自定义实现:简化版 ReentrantLock
为了更深入理解 AQS 原理,我们可以实现一个简化版的 ReentrantLock:
public class SimpleReentrantLock implements Lock {
private final Sync sync;
/**
* 默认创建非公平锁
*/
public SimpleReentrantLock() {
sync = new NonfairSync();
}
/**
* 根据参数创建公平锁或非公平锁
*/
public SimpleReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
/**
* 继承AQS的同步器实现
*/
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
/**
* 非公平的方式获取锁
*/
final boolean unfairTryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// 获取当前state状态
int c = getState();
// state为0表示锁未被持有
if (c == 0) {
// 使用CAS尝试将state从0设置为1
if (compareAndSetState(0, acquires)) {
// 成功获取锁,设置当前持有锁的线程为当前线程
setExclusiveOwnerThread(current);
return true;
}
}
// 如果当前线程就是持有锁的线程,实现可重入
else if (current == getExclusiveOwnerThread()) {
// 增加state值实现重入计数
int nextC = c + acquires;
// 检查溢出
if (nextC < 0) {
throw new Error("Maximum lock count exceeded");
}
// 设置新的state值,这里不需要CAS因为当前线程已经持有锁
setState(nextC);
return true;
}
// 获取锁失败
return false;
}
/**
* 释放锁
*/
@Override
protected final boolean tryRelease(int releases) {
// 检查当前线程是否是持有锁的线程
if (Thread.currentThread() != getExclusiveOwnerThread()) {
throw new IllegalMonitorStateException();
}
// 减少state值
int c = getState() - releases;
// 判断是否完全释放锁
boolean free = (c == 0);
if (free) {
// 完全释放锁,清空持有锁的线程
setExclusiveOwnerThread(null);
}
// 更新state值
setState(c);
return free;
}
/**
* 判断当前线程是否持有锁
*/
@Override
protected boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
/**
* 创建条件变量
*/
Condition newCondition() {
return new ConditionObject();
}
/**
* 获取锁的持有计数
*/
public int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
}
/**
* 公平锁的实现
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
@Override
protected boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 公平性体现:先检查队列中是否有前驱节点在等待
if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
} else if (current == getExclusiveOwnerThread()) {
int nextC = c + acquires;
if (nextC < 0) {
throw new Error("Maximum lock count exceeded");
}
setState(nextC);
return true;
}
return false;
}
}
/**
* 非公平锁的实现
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* 非公平锁的获取实现
*/
@Override
protected boolean tryAcquire(int acquires) {
return unfairTryAcquire(acquires);
}
}
// 实现Lock接口的方法
@Override
public void lock() {
sync.acquire(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
public boolean tryLock() {
return sync.unfairTryAcquire(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(time));
}
@Override
public void unlock() {
sync.release(1);
}
@Override
public Condition newCondition() {
return sync.newCondition();
}
/**
* 查询当前锁是否被某个线程持有
*/
public boolean isLocked() {
return sync.isHeldExclusively();
}
/**
* 查询当前线程是否持有锁
*/
public boolean isHeldByCurrentThread() {
return sync.isHeldExclusively();
}
/**
* 获取当前锁的持有计数
*/
public int getHoldCount() {
return sync.getHoldCount();
}
}
七、Condition 实现原理
AQS 提供了 ConditionObject 内部类,用于实现 Condition 接口,支持类似 wait/notify 的条件等待/通知机制:
- 条件队列:每个 Condition 维护一个单独的条件队列,与 AQS 同步队列相互独立
- await 操作:将当前线程加入条件队列,并释放持有的锁
- signal 操作:将条件队列中的线程转移到同步队列,等待重新获取锁
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// 添加到条件队列
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)
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
八、AQS 的应用场景
AQS 广泛应用于 Java 并发包中的各种同步器:
- ReentrantLock:可重入独占锁
- Semaphore:信号量,控制同时访问特定资源的线程数量
- CountDownLatch:闭锁,允许一个或多个线程等待一组操作完成
- ReentrantReadWriteLock:读写锁,允许多个线程同时读,但只允许一个线程写
- CyclicBarrier:循环栅栏,允许一组线程相互等待达到一个共同点
九、总结
AQS 是 Java 并发框架中最核心的基础组件,它通过以下机制实现了高效的线程同步:
- 状态管理:使用 volatile 变量和 CAS 操作保证线程安全
- 队列管理:使用 CLH 队列高效管理等待线程
- 阻塞原语:使用 LockSupport 实现线程的阻塞和唤醒
- 模板方法模式:将通用逻辑和特定逻辑分离,提高可扩展性
理解 AQS 的工作原理,不仅有助于更好地使用 Java 并发包中的同步器,也能帮助我们在必要时实现自己的高效同步器。AQS 通过简洁的设计将复杂的同步器问题分解为少量的基本方法,使得开发者能够快速实现各种同步器。ReentrantLock 相比 synchronized 提供了更多的功能,如可中断、超时等待、公平性选择等。
深入理解 Java AQS 原理与 ReentrantLock 实现的更多相关文章
- 图解AQS原理之ReentrantLock详解-非公平锁
概述 并发编程中,ReentrantLock的使用是比较多的,包括之前讲的LinkedBlockingQueue和ArrayBlockQueue的内部都是使用的ReentrantLock,谈到它又不能 ...
- 深入理解JAVA虚拟机原理之Dalvik虚拟机(三)
更多Android高级架构进阶视频学习请点击:https://space.bilibili.com/474380680 本文是Android虚拟机系列文章的第三篇,专门介绍Andorid系统上曾经使用 ...
- 并发编程之美,带你深入理解java多线程原理
1.什么是多线程? 多线程是为了使得多个线程并行的工作以完成多项任务,以提高系统的效率.线程是在同一时间需要完成多项任务的时候被实现的. 2.了解多线程 了解多线程之前我们先搞清楚几个重要的概念! 如 ...
- 深入理解JAVA多态原理
之前一直知道多态是什么东西,平时敲代码也经常用到多态,但一直没有真正了解多态底层的运行机制到底是怎么样的,这两天才研究明白点,特地写下来,跟各位同学一起进步,同时也希望各位大神指导和指正. 多态的概念 ...
- 深入理解Java 注解原理
*注解的用途 注解(Annotation)是JDK1.5引入的新特性,包含在java.lang.annotation包中,它是附加在代码中的一些元信息,将一个类的外部信息与内部成员联系起来,在编 译. ...
- 【转】深入理解Java多态原理
之前一直知道多态是什么东西,平时敲代码也经常用到多态,但一直没有真正了解多态底层的运行机制到底是怎么样的,这两天才研究明白点,特地写下来,跟各位同学一起进步,同时也希望各位大神指导和指正. 多态的概念 ...
- 深入理解JAVA虚拟机原理之内存分配策略(二)
更多Android高级架构进阶视频学习请点击:https://space.bilibili.com/474380680 1.对象优先在Eden分配 大多情况,对象在新生代Eden区分配.当Eden区没 ...
- 深入理解JAVA虚拟机原理之垃圾回收器机制(一)
更多Android高级架构进阶视频学习请点击:https://space.bilibili.com/474380680 对于程序计数器.虚拟机栈.本地方法栈这三个部分而言,其生命周期与相关线程有关,随 ...
- 深入理解Java并发之synchronized实现原理
深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型(@Annotation) 深入理解Java类加载器(ClassLoader) 深入 ...
- Java 重入锁 ReentrantLock 原理分析
1.简介 可重入锁ReentrantLock自 JDK 1.5 被引入,功能上与synchronized关键字类似.所谓的可重入是指,线程可对同一把锁进行重复加锁,而不会被阻塞住,这样可避免死锁的产生 ...
随机推荐
- 抖音技术分享:飞鸽IM桌面端基于Rust语言进行重构的技术选型和实践总结
本文由ELab团队公众号授权发布,原题<Rust语言在IM客户端的实践>,来自抖音电商前端团队的分享,本文有修订和改动. 1.引言 本文将介绍飞鸽IM前端团队如何结合Rust对飞鸽客户端接 ...
- OpenMMLab AI实战营 第七课笔记
OpenMMLab AI实战营 第七课笔记 目录 OpenMMLab AI实战营 第七课笔记 import os import numpy as np from PIL import Image im ...
- 关于前端上传excell时间的问题
当前端导入excell里的数据时,只能获取到下面类似的这种数据 Excel存储的日期是从1900年1月1日开始按天数来计算的,也就是说1900年1月1日在Excel中是1. 转化的思路和对Excel中 ...
- python SQLAlchemy ORM——从零开始学习 01 安装库
01基础库 1-1安装 依赖库:sqlalchemy pip install sqlalchemy #直接安装即可 1-2导入使用 这里讲解思路[个人的理解],具体写其实就是这个框架: 导入必要的接口 ...
- Solution Set -「NOIP Simu.」20221003
\(\mathscr{A}\sim\) 二分图排列 定义一个数列 \(\{a_n\}\) 合法, 当且仅当无向图 \(G=(\{1..n\},\{(i,j)\mid i<j\land a_i ...
- 解决StringBuilder readline阻塞问题
readline之所以会阻塞socket流没有结束符 阻塞场景:read() 没有读取到任何数据 readLine() 没有读取到结束符或者换行符 可以用ready判断通道中数据是否读完,读完返回fa ...
- 本地部署DeepSeek
没想到新年最热闹的地方之一会是互联网,刷爆朋友圈的除了新年祝福还有DeepSeek.揣着一颗好奇心试了试,竟有一种发现新大路的感觉.估计是围观的人太多,在线的版本有时候会出现连不上的情况,好奇心驱使之 ...
- Flink CDC全量和增量同步数据如何保证数据的一致性
Apache Flink 的 Change Data Capture (CDC) 功能主要用于实时捕获数据库中的变更记录,并将其转换为事件流以供下游处理.为了保证全量和增量数据同步时数据的一致性.不丢 ...
- C#实现文件的压缩和解压缩
原文链接:https://www.cnblogs.com/sunyaling/archive/2009/04/13/1434602.html 在C#中实现文件的压缩和解压缩,需要使用第三方的组建完成. ...
- IGM机器人K5齿轮箱维修故障详情介绍
在长期.高强度的工作中,IGM机器人K5齿轮箱难免会出现故障,需要联系子锐机器人维修进行及时的维修和保养. 一.齿轮磨损 齿轮磨损是IGM机器人K5齿轮箱最常见的故障之一.长时间.高速运转以及负载的频 ...