Java锁机制深度解析:从synchronized到StampedLock
在并发编程中,锁是保障线程安全的核心机制。本文将系统剖析Java中各类锁的实现原理、适用场景及性能差异,助你精准选择并发控制方案。
一、锁的本质与核心作用
在多线程环境中,锁用于解决两类问题:
- 互斥(Mutual Exclusion):保证同一时刻只有一个线程访问共享资源
- 可见性(Visibility):确保共享变量的修改对其他线程立即可见
Java内存模型(JMM)通过happens-before
原则保证锁的可见性:
解锁操作 happens-before 后续的加锁操作
二、内置锁:synchronized 关键字
1. 实现原理
public synchronized void method() { // 临界区
}// 等价于
public void method() {this.intrinsicLock.lock();try {// 临界区} finally {this.intrinsicLock.unlock();}
}
- 对象头Mark Word:锁状态存储在对象头中(无锁/偏向锁/轻量级锁/重量级锁)
- Monitor监视器:每个对象关联一个Monitor(C++实现)
- 锁升级路径:无锁 → 偏向锁 → 轻量级锁 → 重量级锁
2. 锁升级过程(JDK 6+优化)
锁状态 | 特点 | 适用场景 |
---|---|---|
偏向锁 | 仅第一次CAS设置线程ID | 单线程访问场景 |
轻量级锁 | 自旋尝试获取锁(避免OS阻塞) | 低竞争、短临界区 |
重量级锁 | 通过OS互斥量阻塞线程 | 高竞争、长临界区 |
⚠️ 锁降级在HotSpot中不会发生
三、显式锁:Lock接口及其实现
1. ReentrantLock(可重入锁)
Lock lock = new ReentrantLock();
lock.lock();
try {// 临界区
} finally {lock.unlock(); // 必须放在finally块
}
核心特性:
- 可重入:同线程可多次获取锁(记录重入次数)
- 公平性:支持公平/非公平模式(默认非公平)
- 可中断:
lockInterruptibly()
支持中断等待 - 超时机制:
tryLock(long time, TimeUnit unit)
2. ReentrantReadWriteLock(读写锁)
ReadWriteLock rwLock = new ReentrantReadWriteLock();
Lock readLock = rwLock.readLock(); // 共享锁
Lock writeLock = rwLock.writeLock(); // 排他锁
锁降级示例:
writeLock.lock();
try {// 写操作...readLock.lock(); // 锁降级开始
} finally {writeLock.unlock();
}
try {// 读操作(仍受读锁保护)
} finally {readLock.unlock();
}
⚠️ 不支持锁升级(读锁→写锁会死锁)
四、AQS(AbstractQueuedSynchronizer)
所有显式锁的底层实现基础(JDK 15中60%的并发类依赖AQS)
AQS核心结构
public abstract class AbstractQueuedSynchronizer {// 同步状态(volatile int)private volatile int state;// CLH队列(双向链表)private transient volatile Node head;private transient volatile Node tail;
}
关键操作流程
五、锁的性能优化策略
1. 减小锁粒度
// 错误示例:整个方法加锁
public synchronized void process(Data data) { /*...*/ }// 改进方案:只锁必要部分
public void process(Data data) {preProcess();synchronized(this) {// 仅同步核心逻辑}postProcess();
}
2. 锁分段技术(如ConcurrentHashMap)
// JDK 7实现:Segment数组
final Segment<K,V>[] segments;static final class Segment<K,V> extends ReentrantLock {// 每个Segment独立加锁
}
3. ThreadLocal避免锁竞争
private static final ThreadLocal<SimpleDateFormat> formatter = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
六、特殊锁机制详解
1. StampedLock(JDK 8+)
StampedLock sl = new StampedLock();// 乐观读(不阻塞写线程)
long stamp = sl.tryOptimisticRead();
if (!sl.validate(stamp)) { // 检查期间是否有写操作stamp = sl.readLock(); // 升级为悲观读锁try {// 重新读取} finally {sl.unlockRead(stamp);}
}
三种访问模式:
模式 | 方法 | 特点 |
---|---|---|
写锁 | writeLock() | 独占访问 |
悲观读锁 | readLock() | 共享访问 |
乐观读 | tryOptimisticRead() | 无锁读(需验证) |
2. Condition条件变量
Lock lock = new ReentrantLock();
Condition notEmpty = lock.newCondition();// 生产者
lock.lock();
try {while (queue.isFull())notEmpty.await(); // 释放锁并等待// 生产数据...notFull.signal(); // 唤醒消费者
} finally {lock.unlock();
}
七、锁的注意事项
死锁预防:
- 避免嵌套锁(lock nesting)
- 使用
tryLock
设置超时 - 统一加锁顺序
性能监控:
# 查看锁竞争情况 jstack <PID> | grep -A 10 "BLOCKED"# JFR记录锁事件 jcmd <PID> JFR.start duration=60s filename=lock.jfr
最佳实践:
- 读多写少 → ReentrantReadWriteLock/StampedLock - 写多读少 → ReentrantLock - 简单同步 → synchronized - 无锁算法 → Atomic*/LongAdder
八、锁与内存语义
操作 | 内存语义 | 等效操作 |
---|---|---|
锁释放 | 将本地内存修改刷新到主内存 | volatile写 |
锁获取 | 使本地缓存失效,从主内存读取 | volatile读 |
这也是为什么
ReentrantLock
能保证可见性
九、总结:锁的选择决策树
高级功能包括:
- 可中断锁
- 超时获取
- 公平锁
- 条件等待
推荐诊断工具:
- Arthas:
monitor
命令监控方法锁竞争 - JConsole:线程死锁检测
- Java Flight Recorder:分析锁竞争事件
记住:锁优化永无止境,但无锁才是终极目标。