当前位置: 首页 > news >正文

深入理解 ReentrantLock和AQS底层源码

🔒Java 并发编程:深入理解 ReentrantLock 加锁流程与线程通信机制(含顺序打印 ABC 案例)

本文全面剖析 Java 中 ReentrantLock 的加锁原理,重点分析非公平锁的底层流程以及 **AbstractQueuedSynchronizer( AQS )**思想 并通过经典的“顺序打印 ABC”案例讲解 wait/notify 的线程通信机制。同时对比 synchronized 与 Lock 的应用场景及优劣,适合有一定基础的 Java 开发者阅读与收藏。

继承关系图:

  • 红色代表:内部类
  • 蓝色代表:继承关系
    在这里插入图片描述

🧠一、ReentrantLock 非公平锁的加锁流程详解

1. ReentrantLock 默认是 非公平锁

当创建 ReentrantLock 实例时候,构造函数初始化默认:NonfairSync(非公平锁)

public ReentrantLock() {sync = new NonfairSync();
}

非公平锁不保证线程获取锁的顺序,可能会“插队”,性能更高,但存在一定“饥饿”风险。


2. 加锁入口 lock() 方法

public void lock() {sync.lock();
}

默认调用 ReentrantLock 内部类 Sync#lock 方法:

final void lock() {if (!initialTryLock())acquire(1);
}

3. 第一次尝试加锁(无等待队列)

final boolean initialTryLock() {Thread current = Thread.currentThread();if (compareAndSetState(0, 1)) { // 尝试获取锁setExclusiveOwnerThread(current); // 设置当前线程为持有者return true;} else if (getExclusiveOwnerThread() == current) {// 当前线程已持有锁,支持重入int c = getState() + 1;setState(c);return true;} else {return false;}
}
  • 利用 CAS 修改 state 为 1,表示获取到锁
  • 如果当前线程是锁的持有者,说明是可重入锁,state++
  • 否则返回 false,进入下一阶段

4. 获取失败 → 加入等待队列

调用 AQS#acquire(1)

public final void acquire(int arg) {if (!tryAcquire(arg)) {acquire(null, arg, false, false, false, 0L);}
}

继续调用 NonfairSync#tryAcquire() 再尝试一次:

protected final boolean tryAcquire(int acquires) {if (getState() == 0 && compareAndSetState(0, acquires)) {setExclusiveOwnerThread(Thread.currentThread());return true;}return false;
}

第二次尝试获取锁失败后,开始调用AQS#acquire,将当前线程封装为 Node 加入 CLH 队列

/*** AQS的核心获取锁方法,支持共享和独占模式,支持可中断和超时* * @param node 当前线程对应的节点(可能为null,会在方法内创建)* @param arg 获取锁的参数(通常为1)* @param shared 是否为共享模式(false为独占模式)* @param interruptible 是否可被中断* @param timed 是否有超时限制* @param time 超时的绝对时间(纳秒)* @return 1表示成功获取,负数表示被取消或超时*/
final int acquire(Node node, int arg, boolean shared,boolean interruptible, boolean timed, long time) {Thread current = Thread.currentThread();// 自旋相关变量:用于减少不必要的park/unpark操作byte spins = 0, postSpins = 0;   // 重试次数控制,在第一个线程unpark后使用// 状态标记变量boolean interrupted = false;      // 是否被中断过boolean first = false;           // 当前节点是否是队列中的第一个等待节点Node pred = null;                // 当前节点入队后的前驱节点/** 主循环逻辑(无限循环直到获取成功或被取消):* 1. 检查当前节点是否为队列中第一个等待的节点*    - 如果是,确保head稳定;否则确保前驱节点有效* 2. 如果是第一个节点或还未入队,尝试获取锁* 3. 如果节点还未创建,创建节点* 4. 如果还未入队,尝试入队一次* 5. 如果从park中唤醒,重试(最多postSpins次)* 6. 如果WAITING状态未设置,设置状态并重试* 7. 否则park当前线程,清除WAITING状态,并检查是否被取消*/for (;;) {// === 第一步:检查节点状态和位置 ===// 如果当前不是第一个节点,且节点已存在,获取前驱节点if (!first && (pred = (node == null) ? null : node.prev) != null &&!(first = (head == pred))) { // 检查是否变成了第一个节点// 前驱节点已被取消(状态 < 0)if (pred.status < 0) {cleanQueue();           // 清理队列中被取消的节点continue;               // 重新开始循环} // 前驱节点的prev为null,可能正在初始化else if (pred.prev == null) {Thread.onSpinWait();    // 自旋等待,确保序列化continue;}}// === 第二步:尝试获取锁 ===// 如果是第一个等待节点或还未入队,尝试获取锁if (first || pred == null) {boolean acquired;try {// 根据共享/独占模式调用不同的获取方法if (shared)acquired = (tryAcquireShared(arg) >= 0);elseacquired = tryAcquire(arg);} catch (Throwable ex) {// 获取过程中出现异常,取消当前节点并重新抛出异常cancelAcquire(node, interrupted, false);throw ex;}// === 获取锁成功 ===if (acquired) {// 如果是第一个节点,需要更新head指针if (first) {node.prev = null;     // 清除前驱引用head = node;          // 设置为新的headpred.next = null;     // 断开原head的next引用,帮助GCnode.waiter = null;   // 清除等待线程引用// 如果是共享模式,需要唤醒后续等待的共享节点if (shared)signalNextIfShared(node);// 如果之前被中断过,恢复中断状态if (interrupted)current.interrupt();}return 1; // 成功获取锁}}// === 第三步:创建节点 ===if (node == null) {                 // 节点还未创建,先创建节点再重试入队if (shared)node = new SharedNode();    // 创建共享模式节点elsenode = new ExclusiveNode(); // 创建独占模式节点} // === 第四步:尝试入队 ===else if (pred == null) {            // 节点已创建但还未入队node.waiter = current;          // 设置等待线程Node t = tail;                  // 获取当前尾节点node.setPrevRelaxed(t);         // 设置前驱节点(使用relaxed模式避免不必要的内存屏障)if (t == null)tryInitializeHead();        // 队列为空,初始化head节点else if (!casTail(t, node))     // CAS尝试设置为新的tailnode.setPrevRelaxed(null);  // CAS失败,回退prev设置elset.next = node;              // CAS成功,设置原尾节点的next指针} // === 第五步:自旋优化 ===else if (first && spins != 0) {     // 如果是第一个节点且还有自旋次数--spins;                        // 减少自旋次数,减少重新等待时的不公平性Thread.onSpinWait();            // 自旋等待提示} // === 第六步:设置等待状态 ===else if (node.status == 0) {        // 节点状态为初始状态node.status = WAITING;          // 设置为等待状态,允许被signal唤醒} // === 第七步:阻塞等待 ===else {long nanos;// 计算下次自旋次数:每次左移1位再加1,实现指数增长spins = postSpins = (byte)((postSpins << 1) | 1);if (!timed)// 无超时限制,无限期阻塞LockSupport.park(this);else if ((nanos = time - System.nanoTime()) > 0L)// 有超时限制且还未超时,阻塞指定时间LockSupport.parkNanos(this, nanos);else// 已经超时,退出循环break;// 被唤醒后清除等待状态node.clearStatus();// 检查中断状态if ((interrupted |= Thread.interrupted()) && interruptible)break; // 如果被中断且允许中断响应,退出循环}}// 循环结束表示获取失败(超时或被中断),取消当前节点return cancelAcquire(node, interrupted, interruptible);
}

📌加锁流程图

Thread.lock()│▼
Sync.lock()│├─► NonfairSync#initialTryLock try CAS state == 0 ? ➜ 成功 → 设置 owner → ✅ 加锁成功│└─► 若失败 → 调用 AQS#acquire(1)│├─► NonfairSync#tryAcquire() 再尝试一次获取锁│└─► 失败 AQS#acquire(null, arg, false, false, false, 0L);→ 入队,创建 Node 封装线程│├─► park 当前线程├─► 等待前驱释放锁

🏷️二、ReentrantLock 特性总结

特性说明
可中断使用 lockInterruptibly() 可响应中断,避免死锁
可超时使用 tryLock(timeout) 限时获取锁
可重入同一线程可以多次获取同一把锁,不会死锁
支持公平锁构造函数传入 true 实现公平加锁
条件变量支持可使用 newCondition() 实现类似 wait/notify 的线程通信机制

✅打断示例

Thread t1 = new Thread(() -> {try {lock.lockInterruptibly();} catch (InterruptedException e) {System.out.println("被打断了");return;}try {System.out.println("获取到了锁");} finally {lock.unlock();}
});
lock.lock();
t1.start();
Thread.sleep(1000);
t1.interrupt(); // 打断 t1 阻塞状态

✅设置超时时间

if (lock.tryLock(2, TimeUnit.SECONDS)) {// 成功获取锁
} else {// 获取失败,避免永久阻塞
}

✅公平锁使用

ReentrantLock lock = new ReentrantLock(true); // 公平锁,先进先出

📚三、wait/notify 与 Lock 条件变量对比

1. CPU 空轮询问题(不推荐)

static boolean t2runned = false;
Thread t1 = new Thread(() -> {while (!t2runned); // ❌浪费 CPUSystem.out.println("1");
});
Thread t2 = new Thread(() -> {System.out.println("2");t2runned = true;
});

2. 使用 wait() / notify() 实现线程通信 ✅

public class test5 {static boolean t2runned = false;static final Object lock = new Object();public static void main(String[] args) {Thread t1 = new Thread(() -> {synchronized (lock) {while (!t2runned) {try {lock.wait(); // 进入等待队列} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("1");}});Thread t2 = new Thread(() -> {synchronized (lock) {System.out.println("2");t2runned = true;lock.notify(); // 唤醒 t1}});t1.start();t2.start();}
}

🎯四、实战:多线程顺序打印 ABC(经典面试题)

public class test6 {public static void main(String[] args) {waitNotify waitNotify = new waitNotify(1, 5);new Thread(() -> waitNotify.print("a", 1, 2)).start();new Thread(() -> waitNotify.print("b", 2, 3)).start();new Thread(() -> waitNotify.print("c", 3, 1)).start();}
}class waitNotify {private int flag;private int loopNum;waitNotify(int flag, int loopNum) {this.flag = flag;this.loopNum = loopNum;}public void print(String str, int waitFlag, int nextFlag) {for (int i = 0; i < loopNum; i++) {synchronized (this) {while (flag != waitFlag) {try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.print(str); // 打印对应字符flag = nextFlag;       // 更新下一个线程this.notifyAll();      // 唤醒全部线程}}}
}

🧾输出结果:

abcabcabcabcabc

✅五、总结:并发控制核心对比

特性synchronizedReentrantLock
可重入
响应中断lockInterruptibly()
可超时tryLock(timeout)
公平/非公平选择✅ 可选
条件变量一个支持多个 Condition
性能较低,受限于 JVM 实现高,适合复杂并发逻辑

🚀应用场景推荐

  • 使用 synchronized 简单快捷,适合代码结构清晰、锁粒度小的情况。

  • 使用 ReentrantLock 适合:

    • 多条件变量
    • 超时/中断控制
    • 公平加锁需求
    • 更复杂的并发控制场景
http://www.dtcms.com/a/319391.html

相关文章:

  • 专题:2025财务转型与AI赋能数字化报告|附30+份报告PDF汇总下载
  • 《深入解析缓存三大难题:穿透、雪崩、击穿及应对之道》
  • cv2.threshold cv2.morphologyEx
  • 宝塔面板配置Nacos集群
  • Plant Biotechnol J(IF=10.5)|DAP-seq助力揭示葡萄白粉病抗性机制
  • 什么是POE接口?通俗理解
  • Pytest项目_day07(pytest)
  • MySql MVCC的原理总结
  • S7-1200 串行通信介绍
  • 配送算法9 A GRASP algorithm for the Meal Delivery Routing Problem
  • React 中 useRef 使用方法
  • 设计模式 观察者模式
  • react-router/react-router-dom
  • 对话访谈|盘古信息×冠捷科技:全球制造标杆的智能化密码
  • 鸿蒙类型转化Json转map
  • 【实录】NestJS 中的 IoC
  • 动力电池点焊机:效率质量双提升,驱动新能源制造升级
  • 中小制造企业数字化转型的可持续发展:IT架构演进与管理模式迭代
  • [盛最多水的容器]
  • WPS定制设置成绿色软件
  • Go语言Ebiten坦克大战
  • ADC常用库函数(STC8系列)
  • 现代制冷系统核心技术解析:从四大件到智能控制的关键突破
  • 客户管理系统的详细项目框架结构
  • 从房地产企业运作观企业能力建设
  • (第八期)VS Code 网页开发入门指南:从零开始掌握前端开发工具
  • Leetcode——菜鸟笔记2(移动0)
  • 92. 反转链表 II
  • 【实时Linux实战系列】实时分布式计算架构的实现
  • DataEase官方出品丨SQLBot:基于大模型和RAG的智能问数系统