在并发编程中,锁是保障线程安全的核心机制。本文将系统剖析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:分析锁竞争事件
记住:锁优化永无止境,但无锁才是终极目标。
895

被折叠的 条评论
为什么被折叠?



