锁机制 (Lock Mechanism)¶
深入理解ReentrantLock、ReadWriteLock、StampedLock等显式锁的实现原理和使用场景
目录¶
1. 为什么需要显式锁 (Why Explicit Locks?)¶
1.1 synchronized的局限性¶
虽然synchronized关键字简单易用,但在某些场景下存在局限性:
- 无法中断 - 获取锁的线程无法被中断
- 无法超时 - 无法设置获取锁的超时时间
- 非公平锁 - 默认是非公平锁,可能导致线程饥饿
- 功能单一 - 只有一种锁模式,无法实现读写分离
1.2 显式锁的优势¶
**显式锁(Explicit Lock)**提供了更灵活的锁机制:
- 可中断 - 可以响应中断
- 可超时 - 可以设置超时时间
- 公平锁 - 支持公平锁和非公平锁
- 读写分离 - ReadWriteLock支持读写分离
- 条件变量 - 支持多个条件变量
1.3 在算力平台中的应用¶
在算力平台的结算系统中,需要更灵活的锁机制:
/**
* 算力平台中的锁应用场景
* Lock Usage in Computing Platform
*/
public class PlatformLock {
// 场景1:需要超时的锁操作
private ReentrantLock billingLock = new ReentrantLock();
public boolean tryDeduct(Long userId, Long amount, long timeout) {
try {
// 尝试获取锁,设置超时时间
if (billingLock.tryLock(timeout, TimeUnit.SECONDS)) {
try {
// 执行扣费操作
return deduct(userId, amount);
} finally {
billingLock.unlock();
}
}
return false; // 超时未获取到锁
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
// 场景2:读写分离的场景
private ReadWriteLock configLock = new ReentrantReadWriteLock();
public String getConfig(String key) {
configLock.readLock().lock(); // 读锁
try {
return configMap.get(key);
} finally {
configLock.readLock().unlock();
}
}
public void updateConfig(String key, String value) {
configLock.writeLock().lock(); // 写锁
try {
configMap.put(key, value);
} finally {
configLock.writeLock().unlock();
}
}
}
2. ReentrantLock (ReentrantLock)¶
2.1 核心特点¶
**ReentrantLock(可重入锁)**是java.util.concurrent.locks包中最常用的锁实现。
| 特性 | 说明 |
|---|---|
| 可重入 | 同一线程可以多次获取锁 |
| 可中断 | 支持中断获取锁的操作 |
| 可超时 | 支持超时获取锁 |
| 公平锁 | 支持公平锁和非公平锁 |
2.2 基本使用¶
import java.util.concurrent.locks.ReentrantLock;
/**
* ReentrantLock基本使用
* Basic Usage of ReentrantLock
*/
public class ReentrantLockDemo {
private final ReentrantLock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock(); // 获取锁
try {
count++;
} finally {
lock.unlock(); // 释放锁(必须在finally中)
}
}
public int getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
}
关键点:
- lock() - 获取锁,如果锁被占用则阻塞
- unlock() - 释放锁,必须在finally块中调用
- 必须手动释放锁,否则会导致死锁
2.3 可中断锁¶
/**
* 可中断锁示例
* Interruptible Lock Example
*/
public class InterruptibleLockDemo {
private final ReentrantLock lock = new ReentrantLock();
public void interruptibleMethod() throws InterruptedException {
lock.lockInterruptibly(); // 可中断的获取锁
try {
// 执行任务
while (true) {
// 如果被中断,会抛出InterruptedException
Thread.sleep(1000);
}
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
InterruptibleLockDemo demo = new InterruptibleLockDemo();
Thread thread = new Thread(() -> {
try {
demo.interruptibleMethod();
} catch (InterruptedException e) {
System.out.println("线程被中断");
}
});
thread.start();
thread.interrupt(); // 中断线程
}
}
2.4 超时锁¶
/**
* 超时锁示例
* Timeout Lock Example
*/
public class TimeoutLockDemo {
private final ReentrantLock lock = new ReentrantLock();
public boolean tryLockWithTimeout(long timeout, TimeUnit unit) {
try {
// 尝试获取锁,设置超时时间
if (lock.tryLock(timeout, unit)) {
try {
// 执行任务
doSomething();
return true;
} finally {
lock.unlock();
}
}
return false; // 超时未获取到锁
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
}
2.5 公平锁 vs 非公平锁¶
/**
* 公平锁 vs 非公平锁
* Fair Lock vs Non-Fair Lock
*/
public class FairLockDemo {
// 公平锁:按照请求锁的顺序分配锁
private ReentrantLock fairLock = new ReentrantLock(true);
// 非公平锁:不保证顺序(默认,性能更好)
private ReentrantLock nonFairLock = new ReentrantLock(false);
public void fairMethod() {
fairLock.lock();
try {
// 公平锁:保证先请求的线程先获得锁
} finally {
fairLock.unlock();
}
}
public void nonFairMethod() {
nonFairLock.lock();
try {
// 非公平锁:可能后请求的线程先获得锁(性能更好)
} finally {
nonFairLock.unlock();
}
}
}
对比:
| 特性 | 公平锁 | 非公平锁 |
|---|---|---|
| 顺序保证 | 按请求顺序分配 | 不保证顺序 |
| 性能 | 较低(需要维护队列) | 较高(默认) |
| 适用场景 | 需要公平性的场景 | 大多数场景(默认) |
2.6 条件变量(Condition)¶
import java.util.concurrent.locks.Condition;
/**
* Condition条件变量示例
* Condition Example
*/
public class ConditionDemo {
private final ReentrantLock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
private boolean ready = false;
public void waitForReady() throws InterruptedException {
lock.lock();
try {
while (!ready) {
condition.await(); // 等待条件满足
}
// 条件满足,继续执行
} finally {
lock.unlock();
}
}
public void setReady() {
lock.lock();
try {
ready = true;
condition.signalAll(); // 唤醒所有等待的线程
} finally {
lock.unlock();
}
}
}
Condition的优势:
- 可以创建多个条件变量
- 更灵活的线程间通信
- 类似Object.wait()和notify(),但更强大
3. ReadWriteLock (ReadWriteLock)¶
3.1 核心特点¶
**ReadWriteLock(读写锁)**将锁分为读锁和写锁,实现读写分离。
| 特性 | 说明 |
|---|---|
| 读锁 | 多个线程可以同时持有读锁 |
| 写锁 | 写锁是排他的,与其他锁互斥 |
| 读写分离 | 读多写少场景性能更好 |
| 降级 | 支持锁降级(写锁降级为读锁) |
3.2 基本使用¶
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* ReadWriteLock基本使用
* Basic Usage of ReadWriteLock
*/
public class ReadWriteLockDemo {
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private Map<String, String> cache = new HashMap<>();
// 读操作:使用读锁
public String get(String key) {
lock.readLock().lock();
try {
return cache.get(key);
} finally {
lock.readLock().unlock();
}
}
// 写操作:使用写锁
public void put(String key, String value) {
lock.writeLock().lock();
try {
cache.put(key, value);
} finally {
lock.writeLock().unlock();
}
}
}
3.3 锁的兼容性¶
3.4 锁降级¶
/**
* 锁降级示例
* Lock Downgrade Example
*/
public class LockDowngradeDemo {
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private volatile boolean update = false;
public void processData() {
lock.readLock().lock();
if (!update) {
lock.readLock().unlock();
// 升级为写锁
lock.writeLock().lock();
try {
if (!update) {
// 更新数据
updateData();
update = true;
}
// 降级为读锁
lock.readLock().lock();
} finally {
lock.writeLock().unlock(); // 释放写锁,但持有读锁
}
}
try {
// 使用读锁读取数据
readData();
} finally {
lock.readLock().unlock();
}
}
}
注意: ReadWriteLock不支持锁升级(读锁升级为写锁),只支持降级。
3.5 在算力平台中的应用¶
/**
* 算力平台中的ReadWriteLock应用
* ReadWriteLock in Computing Platform
*/
public class PlatformReadWriteLock {
private final ReadWriteLock configLock = new ReentrantReadWriteLock();
private Map<String, Object> configMap = new ConcurrentHashMap<>();
// 读配置:频繁操作,使用读锁
public Object getConfig(String key) {
configLock.readLock().lock();
try {
return configMap.get(key);
} finally {
configLock.readLock().unlock();
}
}
// 更新配置:低频操作,使用写锁
public void updateConfig(String key, Object value) {
configLock.writeLock().lock();
try {
configMap.put(key, value);
// 通知配置变更
notifyConfigChange(key, value);
} finally {
configLock.writeLock().unlock();
}
}
// 批量读取配置:读锁允许并发
public Map<String, Object> getAllConfigs() {
configLock.readLock().lock();
try {
return new HashMap<>(configMap); // 返回副本
} finally {
configLock.readLock().unlock();
}
}
}
4. StampedLock (StampedLock)¶
4.1 核心特点¶
**StampedLock(戳记锁)**是JDK 1.8引入的新锁,提供了三种模式:
| 模式 | 说明 | 特点 |
|---|---|---|
| 写锁 | 排他锁 | 与其他锁互斥 |
| 读锁 | 共享锁 | 多个线程可以同时持有 |
| 乐观读 | 无锁读取 | 性能最好,但需要验证 |
4.2 基本使用¶
import java.util.concurrent.locks.StampedLock;
/**
* StampedLock基本使用
* Basic Usage of StampedLock
*/
public class StampedLockDemo {
private final StampedLock lock = new StampedLock();
private double x, y;
// 写锁
public void move(double deltaX, double deltaY) {
long stamp = lock.writeLock(); // 获取写锁,返回戳记
try {
x += deltaX;
y += deltaY;
} finally {
lock.unlockWrite(stamp); // 使用戳记释放锁
}
}
// 读锁
public double distanceFromOrigin() {
long stamp = lock.readLock(); // 获取读锁
try {
return Math.sqrt(x * x + y * y);
} finally {
lock.unlockRead(stamp);
}
}
// 乐观读(性能最好)
public double optimisticDistanceFromOrigin() {
long stamp = lock.tryOptimisticRead(); // 尝试乐观读
double currentX = x, currentY = y;
if (!lock.validate(stamp)) { // 验证戳记是否有效
// 戳记无效,升级为读锁
stamp = lock.readLock();
try {
currentX = x;
currentY = y;
} finally {
lock.unlockRead(stamp);
}
}
return Math.sqrt(currentX * currentX + currentY * currentY);
}
}
4.3 乐观读的优势¶
**乐观读(Optimistic Read)**是无锁读取,性能最好:
/**
* 乐观读示例
* Optimistic Read Example
*/
public class OptimisticReadDemo {
private final StampedLock lock = new StampedLock();
private int value = 0;
public int optimisticRead() {
long stamp = lock.tryOptimisticRead(); // 无锁读取
int currentValue = value;
// 验证期间是否有写操作
if (lock.validate(stamp)) {
return currentValue; // 戳记有效,直接返回
} else {
// 戳记无效,升级为读锁
stamp = lock.readLock();
try {
return value;
} finally {
lock.unlockRead(stamp);
}
}
}
public void write(int newValue) {
long stamp = lock.writeLock();
try {
value = newValue;
} finally {
lock.unlockWrite(stamp);
}
}
}
适用场景: 读多写少,且读操作不需要强一致性。
4.4 StampedLock vs ReadWriteLock¶
| 特性 | ReadWriteLock | StampedLock |
|---|---|---|
| 读锁 | 支持 | 支持 |
| 写锁 | 支持 | 支持 |
| 乐观读 | 不支持 | 支持(性能最好) |
| 锁降级 | 支持 | 不支持 |
| 可重入 | 支持 | 不支持 |
| 性能 | 较好 | 更好(乐观读) |
5. 锁的性能对比 (Lock Performance Comparison)¶
5.1 性能测试场景¶
| 场景 | synchronized | ReentrantLock | ReadWriteLock | StampedLock |
|---|---|---|---|---|
| 高竞争写 | 中等 | 较好 | 较差 | 最好 |
| 高竞争读 | 中等 | 中等 | 最好 | 最好(乐观读) |
| 读多写少 | 中等 | 中等 | 最好 | 最好(乐观读) |
| 写多读少 | 较好 | 较好 | 较差 | 最好 |
5.2 选择建议¶
使用synchronized: - 简单的同步场景 - 不需要高级功能(中断、超时等)
使用ReentrantLock: - 需要中断或超时功能 - 需要公平锁 - 需要多个条件变量
使用ReadWriteLock: - 读多写少场景 - 需要锁降级
使用StampedLock: - 读多写少场景 - 可以容忍乐观读的弱一致性 - 追求最高性能
6. 最佳实践 (Best Practices)¶
6.1 始终在finally中释放锁¶
// ✅ 正确:在finally中释放锁
lock.lock();
try {
// 执行任务
} finally {
lock.unlock();
}
// ❌ 错误:可能忘记释放锁
lock.lock();
// 执行任务
lock.unlock(); // 如果中间抛出异常,锁不会被释放
6.2 避免嵌套锁¶
// ❌ 不推荐:嵌套锁可能导致死锁
lock1.lock();
try {
lock2.lock(); // 如果其他线程以相反顺序获取锁,可能死锁
try {
// ...
} finally {
lock2.unlock();
}
} finally {
lock1.unlock();
}
// ✅ 推荐:统一锁顺序
// 或者使用超时锁
if (lock1.tryLock(5, TimeUnit.SECONDS)) {
try {
if (lock2.tryLock(5, TimeUnit.SECONDS)) {
try {
// ...
} finally {
lock2.unlock();
}
}
} finally {
lock1.unlock();
}
}
6.3 选择合适的锁类型¶
// 读多写少:使用ReadWriteLock或StampedLock
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
// 需要超时:使用ReentrantLock
ReentrantLock lock = new ReentrantLock();
if (lock.tryLock(5, TimeUnit.SECONDS)) {
// ...
}
// 简单场景:使用synchronized
synchronized (this) {
// ...
}
7. 面试高频问题 (Interview Questions)¶
Q1: ReentrantLock和synchronized的区别?¶
答案:
| 特性 | synchronized | ReentrantLock |
|---|---|---|
| 可中断 | 否 | 是 |
| 可超时 | 否 | 是 |
| 公平锁 | 否(非公平) | 是(可选) |
| 条件变量 | 单一(wait/notify) | 多个(Condition) |
| 性能 | JVM优化后较好 | 较好 |
| 使用 | 简单 | 需要手动释放 |
Q2: ReadWriteLock的实现原理?¶
答案: 内部维护读锁计数器和写锁状态,读锁可以共享,写锁排他。
Q3: StampedLock的乐观读是什么?¶
答案: 无锁读取,通过戳记验证是否有写操作,性能最好但需要验证。
Q4: 如何避免死锁?¶
答案: 1. 避免嵌套锁 2. 统一锁顺序 3. 使用超时锁 4. 使用锁超时机制
Q5: 锁降级和锁升级的区别?¶
答案: - 锁降级:写锁降级为读锁(ReadWriteLock支持) - 锁升级:读锁升级为写锁(ReadWriteLock不支持,可能导致死锁)
📖 扩展阅读¶
返回: 07-Java并发编程
上一章: 07-02 - 线程同步与内存模型
下一章: 07-04 - 并发工具类 →