# 锁的本质
锁(Lock)是一种同步机制,用于控制多个线程对共享资源的访问,保证同一时刻只有一个线程可以进入临界区(Critical Section)。
临界区:访问共享资源的代码段。
互斥(Mutex):锁的核心目标是实现互斥访问。
可见性:加锁不仅保证互斥,还保证变量的修改对其他线程可见(结合 synchronized 或 Lock 的内存语义)。
# Java 锁
├── 内置锁(synchronized) → 简单、安全、推荐
├── 显式锁(Lock)
│ ├── ReentrantLock → 可中断、可超时、公平锁
│ ├── ReentrantReadWriteLock → 读写分离
│ └── StampedLock → 乐观读,高性能
└── 其他
├── volatile(轻量级同步,不保证原子性)
└── AtomicInteger 等原子类(CAS 无锁并发)
Java 中常见的锁类型
✅ 1. synchronized
关键字(内置锁 / 监视器锁)
Java 最基础的锁机制,由 JVM 实现。
使用方式:
// 1. 修饰实例方法:锁当前对象 (this)
public synchronized void method() { ... }
// 2. 修饰静态方法:锁类对象 (Counter.class)
public static synchronized void staticMethod() { ... }
// 3. 修饰代码块:指定锁对象
public void block() {
synchronized (this) {
// 临界区
}
}
✅ 特点:
- 自动获取和释放:进入同步块自动加锁,退出时自动释放(即使抛异常)。
- 可重入:同一个线程可以多次获取同一把锁。
- 非公平锁:不保证等待时间最长的线程优先获取锁。
- JVM 层面优化:JDK 1.6+ 引入了 偏向锁、轻量级锁、自旋锁 等优化,性能大幅提升。
✅ 2. ReentrantLock
(显式锁)
主要特点是它的名字所表示的含义——“可重入”。简单来说,如果一个线程已经持有了某个锁,那么它可以再次调用lock()方法而不会被阻塞。
java.util.concurrent.locks.ReentrantLock
,是 synchronized
的增强版。
使用方式:
import java.util.concurrent.locks.ReentrantLock;
public class Counter {
private final ReentrantLock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock(); // 手动加锁 可以在同一个线程中调用多次
try {
count++;
} finally {
lock.unlock(); // 必须在 finally 中释放
}
}
}
✅ 特点:
特性 | synchronized | ReentrantLock |
---|---|---|
是否可中断 | ❌ | ✅ lockInterruptibly() |
是否可设置超时 | ❌ | ✅ tryLock(timeout) |
是否公平锁 | ❌(默认非公平) | ✅ 可设置 new ReentrantLock(true) |
是否可多次 tryLock | ❌ | ✅ |
条件变量支持 | wait/notify | Condition (更灵活) |
示例:带超时的锁
if (lock.tryLock(3, TimeUnit.SECONDS)) {
try {
// 执行操作
} finally {
lock.unlock();
}
} else {
// 获取锁失败,做降级处理
}
✅ 3. ReentrantReadWriteLock
(读写锁)
适用于 读多写少 的场景。
- 读锁(共享锁):多个线程可同时获取读锁。
- 写锁(独占锁):写锁是排他的,写时不允许读或写。
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class Cache {
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();
private Map<String, Object> map = new HashMap<>();
public Object get(String key) {
readLock.lock();
try {
return map.get(key);
} finally {
readLock.unlock();
}
}
public void put(String key, Object value) {
writeLock.lock();
try {
map.put(key, value);
} finally {
writeLock.unlock();
}
}
}
⚠️ 注意:读锁不能升级为写锁(会导致死锁),应避免在读锁中尝试获取写锁。
✅ 4. StampedLock
(Java 8+,高性能读写锁)
比 ReentrantReadWriteLock
更高效,支持 乐观读(Optimistic Reading)。
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);
}
}
}
✅ 优点:乐观读不阻塞写线程,适合读操作非常频繁的场景。
四、锁的底层实现机制(简要)
1. synchronized
的优化(JVM 层面)
JDK 1.6+ 引入了锁升级机制:
锁状态 | 触发条件 | 说明 |
---|---|---|
无锁 | 初始状态 | |
偏向锁 | 同一个线程多次进入 | 减少 CAS 操作 |
轻量级锁 | 多个线程竞争不激烈 | 使用 CAS 和栈帧锁记录 |
重量级锁 | 竞争激烈 | 依赖操作系统互斥量(Mutex),性能下降 |
锁会根据竞争情况自动升级,但不会降级。
2. ReentrantLock
的实现
基于 AQS(AbstractQueuedSynchronizer) 框架:
- 使用
volatile
变量表示状态(state); - 线程竞争失败时进入 同步队列(FIFO) 等待;
- 支持公平/非公平策略。
五、锁的性能对比(大致)
锁类型 | 加锁开销 | 适用场景 |
---|---|---|
synchronized (无竞争) | ⚡️ 极低(偏向锁) | 通用,推荐 |
synchronized (竞争激烈) | ⚠️ 较高(重量级锁) | |
ReentrantLock | ⚡️ 低(用户态自旋) | 需要超时、中断、公平性 |
ReentrantReadWriteLock | ⚡️ 读快,写慢 | 读多写少 |
StampedLock | ⚡️⚡️ 最快(乐观读) | 高并发读,偶尔写 |
六、使用锁的注意事项
✅ 最佳实践:
优先使用 synchronized
:简单、安全、JVM 优化充分。需要高级功能时用 ReentrantLock
:如超时、中断、公平锁。读多写少用 ReentrantReadWriteLock 或 StampedLock
。- 锁的粒度要小:只锁必要的代码段,避免“大锁”。
- 避免死锁:
❌ 常见错误:
java深色版本// 错误:锁了不同的对象
synchronized(new Object()) { ... } // 每次都是新对象,无效!
// 错误:忘记释放 ReentrantLock
lock.lock();
// 忘记 try-finally
七、总结:Java 锁体系图谱
深色版本Java 锁
├── 内置锁(synchronized) → 简单、安全、推荐
├── 显式锁(Lock)
│ ├── ReentrantLock → 可中断、可超时、公平锁
│ ├── ReentrantReadWriteLock → 读写分离
│ └── StampedLock → 乐观读,高性能
└── 其他
├── volatile(轻量级同步,不保证原子性)
└── AtomicInteger 等原子类(CAS 无锁并发)
✅ 一句话总结:
“synchronized 简单用,ReentrantLock 灵活控; 读多写少读写锁,StampedLock 高并发; 锁住关键资源,线程安全不落空。”
掌握这些锁的原理和使用场景,你就能写出高效、安全的多线程程序。如果你有具体的并发问题,欢迎提供代码,我可以帮你分析锁的使用是否合理。