文档说明
✅ 严格区分:锁(Lock) vs 同步器(Synchronizer) vs 原子操作
✅ 覆盖 JDK 17 所有锁相关机制(含 JVM 底层优化)
✅ 每类包含:原理深度解析 + 规范代码示例 + 适用场景 + 注意事项
✅ 标注 JDK 17 特性变更(如偏向锁废弃)
✅ 代码经 JDK 17 编译验证,含最佳实践注释
如果不对欢迎指正!最近在复习精进这部分所以写的这个文章
一、锁的完整分类体系

二、核心锁机制详解
1️⃣ synchronized(内置监视器锁)
🔬 原理深度
-
Monitor 机制:每个 Java 对象头(Mark Word)关联 Monitor
-
锁升级路径
(JDK 6+ 优化):
无锁 → 偏向锁 → 轻量级锁(自旋) → 重量级锁(OS Mutex) -
JDK 17 关键变更:
- 偏向锁 默认禁用 (
-XX:-UseBiasedLocking) - 启用需显式参数:
-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0 - 原因:现代应用多线程竞争频繁,偏向锁撤销成本高
- 偏向锁 默认禁用 (
💻 规范示例
java
public class SynchronizedDeepExample {
private int count = 0;
private static final Object LOCK = new Object(); // 推荐:专用锁对象
// 实例方法锁(锁 this)
public synchronized void increment() {
count++;
}
// 静态方法锁(锁 Class 对象)
public static synchronized void staticTask() {
System.out.println("Static synchronized");
}
// 代码块锁(推荐:细粒度控制)
public void safeUpdate(int value) {
synchronized (LOCK) { // 避免锁 this 或 String 常量
count = value;
}
}
// 死锁预防示例:按固定顺序获取锁
public void transfer(SynchronizedDeepExample target, int amount) {
SynchronizedDeepExample first = this.hashCode() < target.hashCode() ? this : target;
SynchronizedDeepExample second = (first == this) ? target : this;
synchronized (first) {
synchronized (second) {
this.count -= amount;
target.count += amount;
}
}
}
}
⚠️ 关键注意事项
| 问题 | 解决方案 |
|---|---|
| 锁粗化 | JVM 自动优化,避免频繁加解锁 |
| 锁消除 | JIT 逃逸分析,无竞争时消除锁 |
| 死锁风险 | 使用 jstack 检测,按固定顺序加锁 |
| 锁对象变更 | 严禁在同步块内修改锁引用 |
2️⃣ ReentrantLock(显式可重入锁)
🔬 原理深度
-
AQS(AbstractQueuedSynchronizer)
核心:
- CLH 队列变体管理等待线程
state字段记录锁持有次数(可重入计数)Node节点含waitStatus(CANCELLED, SIGNAL 等)
-
公平性:
- 非公平锁(默认):新线程尝试插队获取锁
- 公平锁:严格 FIFO,避免线程饥饿(吞吐量略低)
💻 规范示例
java
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockCompleteExample {
private final ReentrantLock lock = new ReentrantLock(true); // 公平锁
private final Condition notEmpty = lock.newCondition();
private final Condition notFull = lock.newCondition();
private int buffer = 0;
private static final int CAPACITY = 10;
// 可中断锁获取
public void interruptibleTask() throws InterruptedException {
lock.lockInterruptibly(); // 响应中断
try {
// 临界区操作
} finally {
lock.unlock();
}
}
// 超时尝试锁
public boolean tryLockWithTimeout() throws InterruptedException {
if (lock.tryLock(2, TimeUnit.SECONDS)) {
try {
// 执行任务
return true;
} finally {
lock.unlock();
}
}
return false; // 超时未获取
}
// 生产者-消费者(Condition 应用)
public void produce() throws InterruptedException {
lock.lock();
try {
while (buffer >= CAPACITY) {
notFull.await(); // 释放锁并等待
}
buffer++;
System.out.println("Produced: " + buffer);
notEmpty.signal(); // 唤醒消费者
} finally {
lock.unlock();
}
}
public void consume() throws InterruptedException {
lock.lock();
try {
while (buffer <= 0) {
notEmpty.await();
}
buffer--;
System.out.println("Consumed: " + buffer);
notFull.signal();
} finally {
lock.unlock();
}
}
// 查询锁状态(调试/监控)
public void printLockInfo() {
System.out.println("Locked: " + lock.isLocked());
System.out.println("Held by current: " + lock.isHeldByCurrentThread());
System.out.println("Hold count: " + lock.getHoldCount());
System.out.println("Queue length: " + lock.getQueueLength());
}
}
✅ 优势对比 synchronized
| 特性 | ReentrantLock | synchronized |
|---|---|---|
| 可中断 | ✅ | ❌ |
| 超时获取 | ✅ | ❌ |
| 公平策略 | ✅ | ❌ |
| 多条件队列 | ✅ (Condition) | ❌ (仅1个wait set) |
| 锁状态查询 | ✅ | ❌ |
| JVM 优化 | 有限 | 深度优化 |
3️⃣ ReentrantReadWriteLock(读写锁)
🔬 原理深度
- AQS 双状态设计:
- 高16位:读锁计数(共享)
- 低16位:写锁计数(独占)
- 写锁降级 :获取写锁 → 获取读锁 → 释放写锁(安全)
- 读锁升级 :获取读锁 → 尝试写锁(死锁风险!禁止)
💻 规范示例
java
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.HashMap;
import java.util.Map;
public class CacheWithReadWriteLock {
private final Map<String, String> cache = new HashMap<>();
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private final ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
private final ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
// 读操作(高并发)
public String get(String key) {
readLock.lock();
try {
return cache.get(key);
} finally {
readLock.unlock();
}
}
// 写操作(独占)
public void put(String key, String value) {
writeLock.lock();
try {
cache.put(key, value);
} finally {
writeLock.unlock();
}
}
// 写锁降级示例(安全模式)
public String computeIfAbsent(String key, java.util.function.Function<String, String> loader) {
readLock.lock();
String value = cache.get(key);
if (value != null) {
readLock.unlock(); // 有缓存直接返回
return value;
}
readLock.unlock(); // 释放读锁
writeLock.lock(); // 升级为写锁(注意:此处已释放读锁,非直接升级)
try {
value = cache.get(key);
if (value == null) {
value = loader.apply(key);
cache.put(key, value);
}
// 降级:先获取读锁,再释放写锁
readLock.lock();
} finally {
writeLock.unlock(); // 释放写锁,保留读锁
}
try {
return value;
} finally {
readLock.unlock(); // 最终释放读锁
}
}
}
⚠️ 重要警告
java
// ❌ 危险:读锁内尝试获取写锁 → 死锁!
readLock.lock();
try {
if (needUpdate) {
writeLock.lock(); // 永远无法获取,当前线程已持读锁
// ...
}
} finally {
readLock.unlock();
}
4️⃣ StampedLock(邮戳锁|JDK 8+)
🔬 原理深度
-
票据(Stamp)机制:64 位 long 值标识锁状态
-
三种模式:
模式 方法 特性 写锁 writeLock()独占,返回 stamp 悲观读 readLock()共享,返回 stamp 乐观读 tryOptimisticRead()无锁,返回 stamp,需验证 -
验证机制 :
validate(stamp)检查期间是否有写入 -
JDK 17 优势 :避免
ReentrantReadWriteLock的写饥饿问题
💻 规范示例
java
import java.util.concurrent.locks.StampedLock;
public class Point {
private double x, y;
private final StampedLock sl = new StampedLock();
// 乐观读(高性能路径)
public double distanceFromOrigin() {
long stamp = sl.tryOptimisticRead(); // ① 乐观读
double currentX = x, currentY = y;
// ② 验证:若验证失败(期间有写入),降级为悲观读
if (!sl.validate(stamp)) {
stamp = sl.readLock(); // ③ 悲观读锁
try {
currentX = x;
currentY = y;
} finally {
sl.unlockRead(stamp);
}
}
return Math.sqrt(currentX * currentX + currentY * currentY);
}
// 写操作
public void move(double deltaX, double deltaY) {
long stamp = sl.writeLock(); // ① 写锁
try {
x += deltaX;
y += deltaY;
} finally {
sl.unlockWrite(stamp); // ② 释放
}
}
// 悲观读示例(需保证读期间数据不变)
public void printCoordinates() {
long stamp = sl.readLock();
try {
System.out.println("Point: (" + x + ", " + y + ")");
} finally {
sl.unlockRead(stamp);
}
}
}
⚠️ 严格限制
| 限制 | 说明 |
|---|---|
| 不可重入 | 同一线程重复获取锁会死锁 |
| 无 Condition | 不支持 newCondition() |
| 中断不安全 | readLock()/writeLock() 不响应中断(用 try... 系列方法) |
| 内存泄漏风险 | 必须保存 stamp 值用于解锁 |
5️⃣ JVM 底层锁优化机制(synchronized 底层)
🔬 锁升级全流程(JDK 17 视角)
无锁状态
偏向锁:
第一个线程访问
偏向锁
轻量级锁:
其他线程竞争(撤销偏向)
轻量级锁
重量级锁:
自旋超时/竞争激烈
无锁状态:
释放锁
重量级锁
JDK 17 默认禁用!
启用需:-XX:+UseBiasedLocking
🔍 各阶段详解
| 阶段 | Mark Word 变化 | 适用场景 | JDK 17 状态 |
|---|---|---|---|
| 无锁 | 对象哈希码 + 分代年龄 | 无竞争 | 常态 |
| 偏向锁 | 线程ID + 偏向时间戳 | 单线程长期持有 | 默认禁用 |
| 轻量级锁 | 指向栈中锁记录的指针 | 短时、低竞争 | 活跃 |
| 重量级锁 | 指向 Monitor 对象指针 | 高竞争、长持有 | 活跃 |
💡 自旋锁机制(JVM 内部)
-
原理:线程在用户态循环 CAS 尝试获取锁(避免挂起开销)
-
JDK 17 优化:
- 自适应自旋:根据历史成功次数动态调整自旋次数
Thread.onSpinWait():提示 CPU 优化自旋(JDK 9+)
java// 自旋等待示例(配合 volatile 使用) volatile boolean flag = false; public void spinWaitExample() { while (!flag) { Thread.onSpinWait(); // JDK 9+:提示 CPU 进入自旋优化模式 } // 执行后续操作 }
6️⃣ 乐观锁(无锁编程)
🔬 原理深度
- CAS(Compare-And-Swap):
- 原子指令:
compareAndSet(expected, update) - 底层:CPU
LOCK CMPXCHG指令(保证原子性)
- 原子指令:
- JDK 17 演进:
Unsafe逐步废弃 →VarHandle成为标准(JDK 9+)Atomic*类内部已迁移至VarHandle
💻 规范示例
java
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.LongAdder;
public class OptimisticLockingExample {
// 方式1:AtomicInteger(封装 CAS)
private final AtomicInteger counter = new AtomicInteger(0);
public void atomicIncrement() {
counter.incrementAndGet(); // 内部 CAS 循环
}
// 方式2:VarHandle(JDK 9+ 推荐,替代 Unsafe)
private volatile long value;
private static final VarHandle VH;
static {
try {
VH = MethodHandles.lookup()
.findVarHandle(OptimisticLockingExample.class, "value", long.class);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void varHandleIncrement() {
long expected;
long update;
do {
expected = value;
update = expected + 1;
} while (!VH.compareAndSet(this, expected, update)); // CAS
}
// 方式3:LongAdder(高并发计数优化)
private final LongAdder adder = new LongAdder();
public void highContentionIncrement() {
adder.increment(); // 分段累加,减少 CAS 冲突
}
// ABA 问题解决方案:AtomicStampedReference
// (示例略,适用于需检测值是否被修改过的场景)
}
📊 乐观锁 vs 悲观锁
| 维度 | 乐观锁 | 悲观锁 |
|---|---|---|
| 冲突假设 | 低冲突 | 高冲突 |
| 阻塞 | 无(失败重试) | 有(挂起线程) |
| 适用场景 | 计数器、状态标志 | 数据库事务、复杂业务 |
| ABA 问题 | 需额外处理(StampedReference) | 不存在 |
7️⃣ 锁支撑工具
🔹 LockSupport(线程阻塞/唤醒基石)
java
import java.util.concurrent.locks.LockSupport;
public class LockSupportExample {
static class Parker extends Thread {
public void run() {
System.out.println("Thread waiting...");
LockSupport.park(); // 挂起(响应中断,但不抛异常)
if (Thread.interrupted()) {
System.out.println("Interrupted!");
}
System.out.println("Thread resumed");
}
}
public static void main(String[] args) throws InterruptedException {
Parker t = new Parker();
t.start();
Thread.sleep(100);
LockSupport.unpark(t); // 精确唤醒指定线程(无许可计数)
}
}
- 原理 :操作线程的
parkBlocker字段(用于诊断) - vs wait/notify :无需持有锁,可先
unpark后park
🔹 Thread.onSpinWait()(JDK 9+)
- 提示 CPU 当前线程处于自旋等待
- 优化能效:CPU 可降低功耗或优化流水线
- 使用场景:自旋锁、无锁队列的等待循环
三、锁选型决策指南
🌐 选型决策流程图

速查表(增强版)
| 场景 | 推荐方案 | 关键代码片段 | 避坑提示 |
|---|---|---|---|
| 单例延迟初始化 | synchronized + 双重检查 |
if (instance == null) { synchronized (lock) { ... } } |
字段必须 volatile |
| 配置热更新(读99%) | StampedLock |
long s = sl.tryOptimisticRead(); if (!sl.validate(s)) s = sl.readLock(); |
验证失败必须降级 |
| 订单状态机流转 | ReentrantLock + Condition |
lock.lock(); try { while (!valid) notValid.await(); } |
避免虚假唤醒(用while) |
| 分布式ID生成器 | LongAdder |
adder.increment(); return base + adder.sum(); |
非强一致性场景 |
| 线程安全本地缓存 | ConcurrentHashMap |
map.computeIfAbsent(key, k -> load(k)); |
优于 手动加锁Map |
| 定时任务调度器 | ScheduledThreadPoolExecutor |
scheduler.scheduleAtFixedRate(task, 0, 1, SECONDS); |
优于 手写锁+wait |
💡 选型心法(一句话总结)
"能用并发容器不用锁,能用synchronized不用显式锁,能用乐观锁不用悲观锁,能重构消除共享状态绝不加锁"
四、JDK 17 锁机制实战指南:原则、陷阱与调优
核心思想 :锁是手段而非目的,目标是正确性 > 可维护性 > 性能。以下内容源自千万级并发系统实战经验,直击生产环境痛点。
4.1 🌟 现代锁使用黄金法则(场景化修订)
| 原则 | 为什么重要 | 实战场景示例 | 反面案例 |
|---|---|---|---|
✅ 简单互斥首选 synchronized |
语法安全、JVM深度优化、调试友好 | 单例双重检查锁、简单计数器 | 为"性能"强行用ReentrantLock,增加维护成本 |
✅ 显式锁必须 try-finally 配对 |
防止异常导致永久死锁 | 数据库连接池获取连接 | lock.lock(); doWork(); lock.unlock();(异常时锁泄漏) |
| ✅ 临界区最小化:锁外计算,锁内赋值 | 减少线程阻塞时间,提升吞吐 | 缓存更新:先计算新值,锁内仅替换引用 | 锁内调用RPC/DB/复杂计算 |
| ✅ 多锁场景:全局统一加锁顺序 | 根本性预防死锁 | 转账场景:按账户ID升序加锁 | 任意顺序加锁导致循环等待 |
✅ 读多写少:StampedLock > ReadWriteLock |
避免写饥饿,乐观读提升吞吐 | 配置中心、实时指标查询 | 用synchronized导致读线程阻塞 |
✅ 高并发计数:LongAdder 替代 AtomicInteger |
分段累加,减少CAS冲突 | 服务QPS统计、埋点计数 | 万级QPS下AtomicInteger性能骤降 |
| ✅ 无锁编程:仅限简单状态机 | 避免ABA、内存屏障复杂性 | 线程状态标志(INIT→RUNNING→STOPPED) | 用CAS实现复杂业务逻辑导致隐蔽Bug |
| ✅ 监控先行:上线必埋点 | 快速定位线上锁竞争 | 通过Micrometer暴露锁等待时间指标 | 问题发生后"盲调" |
4.2 🔍 高频陷阱与解决方案(附修复代码)
❌ 陷阱1:锁对象逃逸导致意外共享
java
// 反面教材:锁字符串常量(所有实例共享同一把锁!)
public void badLock() {
synchronized ("CONFIG_LOCK") { // 所有类实例竞争同一把锁
// ...
}
}
// ✅ 修复方案:使用私有final对象
private final Object configLock = new Object();
public void goodLock() {
synchronized (configLock) {
// 仅当前实例加锁
}
}
❌ 陷阱2:锁内调用外部回调(隐式死锁风险)
java
// 反面教材:锁内调用用户回调
public void process(Order order) {
synchronized (lock) {
validator.validate(order); // 回调可能持有其他锁!
// ...
}
}
// ✅ 修复方案:锁外验证,锁内仅操作内部状态
public void safeProcess(Order order) {
if (!validator.validate(order)) return; // 锁外验证
synchronized (lock) {
// 仅操作内部状态
orders.add(order);
}
}
❌ 陷阱3:忽略中断导致线程无法优雅退出
java
// 反面教材:吞掉中断信号
public void badInterrupt() {
lock.lock();
try {
// ...
} finally {
lock.unlock();
}
}
// ✅ 修复方案:响应中断 + 恢复中断状态
public void safeInterrupt() throws InterruptedException {
if (!lock.tryLock(2, TimeUnit.SECONDS)) {
Thread.currentThread().interrupt(); // 恢复中断状态
throw new InterruptedException("Lock acquisition timeout");
}
try {
// ...
} finally {
lock.unlock();
}
}
❌ 陷阱4:StampedLock 误用(重入/中断)
java
// 反面教材:尝试重入StampedLock(必然死锁!)
public void dangerous() {
long s = sl.writeLock();
try {
long s2 = sl.writeLock(); // 同一线程再次获取 → 永久阻塞!
} finally {
sl.unlockWrite(s);
}
}
// ✅ 修复方案:严格避免重入,或改用ReentrantReadWriteLock
4.3 🛠️ 监控与诊断实战(JDK 17 工具链)
🔹 锁竞争热点定位
bash
# 1. 生成线程转储(定位死锁/阻塞)
jstack -l <pid> > thread_dump.log
# 查看 "java.lang.Thread.State: BLOCKED" 线程及锁持有者
# 2. Async-Profiler 火焰图(精准定位锁等待)
async-profiler/profiler.sh -e lock -d 30 -f lock.svg <pid>
# 生成锁等待火焰图,直观看到热点锁
# 3. JFR(Java Flight Recorder)记录锁事件(JDK 17内置)
java -XX:+FlightRecorder -XX:StartFlightRecording=duration=60s,filename=recording.jfr MyApp
# 用 JDK Mission Control (JMC) 分析:Lock Instances, Monitor Blocked 等事件
🔹 Micrometer 监控埋点(Spring Boot 示例)
java
@Component
public class LockMetrics {
private final Counter lockTimeouts;
private final Timer lockAcquisitionTime;
public LockMetrics(MeterRegistry registry) {
lockTimeouts = Counter.builder("lock.timeouts")
.description("Lock acquisition timeout count")
.register(registry);
lockAcquisitionTime = Timer.builder("lock.acquisition.time")
.description("Time taken to acquire lock")
.register(registry);
}
public <T> T executeWithMetrics(Supplier<T> task, Lock lock) {
long start = System.nanoTime();
boolean acquired = false;
try {
acquired = lock.tryLock(2, TimeUnit.SECONDS);
if (!acquired) {
lockTimeouts.increment();
throw new RuntimeException("Lock timeout");
}
return task.get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
} finally {
if (acquired) {
lockAcquisitionTime.record(System.nanoTime() - start, TimeUnit.NANOSECONDS);
lock.unlock();
}
}
}
}
4.4 🧪 并发测试策略(不止于 jcstress)
| 工具 | 适用场景 | 示例命令 |
|---|---|---|
| jcstress | 验证内存可见性、指令重排 | mvn jcstress:run -Djcstress.forks=4 |
| ThreadSanitizer (TSan) | 检测数据竞争(需GraalVM Native Image) | native-image --enable-thread-sanitizer ... |
| Chaos Engineering | 模拟高负载/网络延迟下的锁行为 | 使用 Chaos Mesh 注入延迟 |
| 压测验证 | 验证锁在峰值流量下的表现 | Gatling/JMeter + 监控锁指标 |
jcstress 简单示例(验证可见性)
java
@JCStressTest
@Outcome(id = "1, 1", expect = Acceptable, desc = "Correct publication")
@State
public class SafePublicationTest {
int x;
volatile boolean ready; // 关键:volatile 保证可见性
@Actor
public void actor1() {
x = 1;
ready = true;
}
@Actor
public void actor2(IntResult2 r) {
r.r1 = ready ? x : -1;
r.r2 = x;
}
}
4.5 📌 JDK 17 专属实践建议
| 场景 | 建议 | 原因 |
|---|---|---|
| 新项目开发 | 直接忽略偏向锁 | JDK 17 默认禁用,无需配置 |
| 遗留系统迁移 | 测试 -XX:-UseBiasedLocking 影响 |
避免因默认禁用导致性能波动 |
| 原子操作 | 优先 VarHandle > Unsafe |
Unsafe 已标记 @Deprecated(forRemoval=true) |
| 自旋优化 | 短等待循环中加入 Thread.onSpinWait() |
提示CPU优化流水线,降低功耗 |
| 容器环境 | 设置 -XX:ActiveProcessorCount |
避免JVM误判CPU核数影响自旋策略 |
| 云原生应用 | 结合 Kubernetes livenessProbe 设置超时 | 避免锁等待导致Pod被误杀 |
4.6 💡 终极心法:何时该用锁?何时不该用?
| 问题 | 决策路径 |
|---|---|
| 需要互斥访问共享可变状态? | → 是:选锁;否:考虑不可变对象/线程局部变量 |
| 临界区是否包含I/O或远程调用? | → 是:重构!将I/O移出锁;否:继续评估 |
| 能否用并发容器替代? | → ConcurrentHashMap > 手动加锁Map;BlockingQueue > 手动实现生产者-消费者 |
| 是否可接受最终一致性? | → 是:考虑异步队列(如Disruptor)、事件溯源 |
| 锁竞争是否成为瓶颈? | → 用Async-Profiler验证;是:考虑分段锁、无锁结构、架构拆分 |
记住 :
🔸 最好的锁是不需要锁 (通过架构设计消除共享状态)
🔸 次好的锁是看不见的锁 (JVM优化后的synchronized)
🔸 最差的锁是"聪明"的锁(过度设计、难以维护的锁逻辑)