锁机制
1443字约5分钟
2025-08-21
一、引言:为什么需要锁?
在多线程环境下,多个线程并发访问共享资源时,可能会导致 数据不一致、竞态条件(Race Condition)、脏读、丢失更新 等问题。
锁(Lock) 是解决这些问题的核心机制,它确保同一时刻只有一个线程可以访问临界资源,从而保证线程安全。
二、Java 中的锁分类
| 分类维度 | 类型 |
|---|---|
| 实现方式 | 内置锁(synchronized)、显式锁(Lock) |
| 共享性 | 独占锁(Exclusive)、共享锁(Shared) |
| 可重入性 | 可重入锁、不可重入锁 |
| 公平性 | 公平锁、非公平锁 |
| 乐观/悲观 | 乐观锁、悲观锁 |
三、内置锁:synchronized
3.1 基本语法
synchronized 是 Java 内置的悲观锁机制,由 JVM 实现,自动获取和释放。
// 1. 修饰实例方法:锁当前对象 this
public synchronized void method() {
// 临界区
}
// 2. 修饰静态方法:锁类对象 Class
public static synchronized void staticMethod() {
// 临界区
}
// 3. 同步代码块:锁指定对象
public void blockMethod() {
synchronized (this) {
// 临界区
}
}3.2 底层原理
- 每个 Java 对象都有一个 监视器锁(Monitor)。
synchronized通过monitorenter和monitorexit字节码指令实现。- JDK 1.6 后引入了 锁升级机制:无锁 → 偏向锁 → 轻量级锁 → 重量级锁。
3.3 特性
- ✅ 自动释放锁(异常时也会释放)
- ✅ 可重入:同一线程可多次进入同一锁
- ✅ 不可中断:线程阻塞时无法被中断
- ⚠️ 非公平:不保证等待线程的执行顺序
四、显式锁:java.util.concurrent.locks.Lock
4.1 Lock 接口核心方法
public interface Lock {
void lock(); // 获取锁,阻塞
void lockInterruptibly() throws InterruptedException; // 可中断获取
boolean tryLock(); // 尝试获取,立即返回
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock(); // 释放锁
Condition newCondition(); // 创建条件变量
}4.2 ReentrantLock:可重入锁
ReentrantLock 是 Lock 的主要实现类,功能比 synchronized 更强大。
基本使用
private final ReentrantLock lock = new ReentrantLock();
public void doSomething() {
lock.lock(); // 手动加锁
try {
// 临界区
System.out.println("执行任务");
} finally {
lock.unlock(); // 必须在 finally 中释放
}
}构造函数:支持公平锁
// 非公平锁(默认,性能更高)
ReentrantLock lock = new ReentrantLock();
// 公平锁(按等待顺序获取锁)
ReentrantLock fairLock = new ReentrantLock(true);⚠️ 公平锁性能较低,仅在需要严格顺序时使用。
特性对比
| 特性 | synchronized | ReentrantLock |
|---|---|---|
| 可重入 | ✅ | ✅ |
| 可中断 | ❌ | ✅ |
| 超时获取 | ❌ | ✅ |
| 公平锁 | ❌ | ✅ |
| 条件变量 | wait/notify | Condition |
| 锁分离 | ❌ | ✅(可多个 Condition) |
| 手动释放 | 自动 | 必须手动(try-finally) |
五、读写锁:ReentrantReadWriteLock
当共享资源 读多写少 时,使用读写锁可大幅提升并发性能。
5.1 原理
- 读锁(ReadLock):共享锁,允许多个线程同时读。
- 写锁(WriteLock):独占锁,写时不允许读或写。
5.2 使用示例
public class Cache {
private final Map<String, Object> map = new HashMap<>();
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();
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();
}
}
}5.3 特性
- ✅ 读写分离,提高并发吞吐量
- ✅ 写锁可降级为读锁(但不能升级)
- ⚠️ 读锁不支持条件变量
六、其他锁机制
6.1 StampedLock(JDK 8+)
比 ReentrantReadWriteLock 更高性能的读写锁,支持 乐观读。
private final StampedLock stampedLock = new StampedLock();
private double x, y;
// 乐观读
public double distanceFromOrigin() {
long stamp = stampedLock.tryOptimisticRead();
double currentX = x, currentY = y;
if (!stampedLock.validate(stamp)) {
// 乐观读失败,升级为悲观读
stamp = stampedLock.readLock();
try {
currentX = x;
currentY = y;
} finally {
stampedLock.unlockRead(stamp);
}
}
return Math.sqrt(currentX * currentX + currentY * currentY);
}
// 写操作
public void move(double deltaX, double deltaY) {
long stamp = stampedLock.writeLock();
try {
x += deltaX;
y += deltaY;
} finally {
stampedLock.unlockWrite(stamp);
}
}✅ 适用于读操作极多、写操作极少的场景。
七、乐观锁与 CAS
7.1 什么是乐观锁?
- 假设没有冲突,只在提交时检查是否被修改。
- 常用实现:CAS(Compare and Swap) + 版本号(如 ABA 问题解决)。
7.2 Atomic 类(基于 CAS)
private AtomicInteger counter = new AtomicInteger(0);
public void increment() {
counter.incrementAndGet(); // 原子自增
}
public int getCounter() {
return counter.get();
}7.3 CAS 原理
compareAndSet(expectedValue, newValue)- 如果当前值等于预期值,则更新为新值,否则失败。
✅ 无锁,性能高
⚠️ ABA 问题:可通过AtomicStampedReference解决
八、锁优化技术
8.1 锁消除(Lock Elimination)
JVM 在运行时检测到不可能存在竞争的锁,自动消除。
public String concat() {
StringBuffer sb = new StringBuffer();
sb.append("Hello");
sb.append("World");
return sb.toString();
}
StringBuffer方法是synchronized的,但局部变量无并发,JVM 可能消除锁。
8.2 锁粗化(Lock Coarsening)
将多个连续的加锁操作合并为一个,减少锁开销。
synchronized(lock) { append("A"); }
synchronized(lock) { append("B"); }
synchronized(lock) { append("C"); }→ JVM 可能优化为:
synchronized(lock) {
append("A");
append("B");
append("C");
}8.3 锁分段(Lock Striping)
如 ConcurrentHashMap 使用分段锁(JDK 7)或 CAS + synchronized(JDK 8+),减少锁竞争。
九、锁的使用建议与最佳实践
- 优先使用
synchronized:简单、安全、自动释放。 - 复杂场景使用
ReentrantLock:需要可中断、超时、公平锁时。 - 读多写少用
ReadWriteLock或StampedLock。 - 高并发计数用
Atomic类。 - 避免死锁:
- 按固定顺序加锁
- 使用超时机制(
tryLock) - 避免锁嵌套
- Always in try-finally:确保锁释放。
- 不要在锁中执行耗时操作:如 I/O、网络调用。
十、常见锁相关面试题
- synchronized 和 ReentrantLock 的区别?
- ReentrantLock 是如何实现可重入的?
- 什么是 ABA 问题?如何解决?
- StampedLock 为什么比读写锁快?
- 锁升级过程是怎样的?
- 如何避免死锁?
十二、总结
| 锁类型 | 适用场景 | 性能 | 复杂度 |
|---|---|---|---|
synchronized | 一般同步 | 中 | 低 |
ReentrantLock | 高级控制 | 高 | 中 |
ReadWriteLock | 读多写少 | 高 | 中 |
StampedLock | 极致读性能 | 极高 | 高 |
Atomic | 原子变量 | 极高 | 低 |
✅ 选择建议:
- 简单同步 →
synchronized- 需要超时/中断 →
ReentrantLock- 读多写少 →
StampedLock- 计数/状态 →
Atomic类
文档持续更新中,欢迎反馈与交流!
