1. 基本概念
悲观锁和乐观锁是并发编程中两种不同的并发控制策略,它们基于对数据竞争的不同假设:
1.1 悲观锁 (Pessimistic Locking)
悲观锁是一种悲观的并发控制策略,它假设数据总是会发生冲突,因此在整个数据操作过程中,会对数据进行加锁,防止其他线程对数据进行修改。
- 核心理念:“先上锁,再操作”,确保操作的独占性
- 适用场景:高冲突环境,即多线程并发访问数据的频率较高
- 代表实现:数据库的行锁、表锁、读锁、写锁;Java中的 synchronized 、 ReentrantLock 等
1.2 乐观锁 (Optimistic Locking)
乐观锁是一种乐观的并发控制策略,它假设数据不会发生冲突,因此不会预先加锁,而是在操作完成后,通过一定的机制来检测是否发生了冲突。
- 核心理念:“先操作,后检测”,通过版本控制或CAS操作实现
- 适用场景:低冲突环境,即多线程并发修改同一数据的频率较低
- 代表实现:数据库的版本号机制、时间戳机制;Java中的原子类、CAS操作等
2. 实现原理与机制
2.1 悲观锁的实现原理
悲观锁的实现依赖于系统提供的锁机制,通过阻塞其他线程来保证数据的一致性:
- 阻塞等待机制:当一个线程持有锁时,其他需要该锁的线程会被阻塞,进入等待队列
- 操作系统支持:底层通常依赖操作系统的互斥量(Mutex)、信号量(Semaphore)等机制
- 锁的粒度:可以分为粗粒度锁(如表锁)和细粒度锁(如行锁)
以数据库为例,当执行以下SQL时,会自动获取悲观锁:
-- MySQL中的行级锁定示例
SELECT * FROM users WHERE id = 1 FOR UPDATE;
2.2 乐观锁的实现原理
乐观锁的实现主要通过以下两种机制:
2.2.1 版本号机制
- 在数据中增加一个版本号(version)字段
- 读取数据时,同时读取版本号
- 更新数据时,比较版本号,如果相同则更新并将版本号加1,如果不同则说明数据已被其他线程修改
-- 乐观锁更新示例
UPDATE users
SET name = 'newName', version = version + 1
WHERE id = 1 AND version = currentVersion;
2.2.2 CAS (Compare-And-Swap) 操作
CAS 是一种无锁算法,包含三个操作数:
- 内存位置(V)
- 预期值(A)
- 新值(B)
只有当内存位置的值等于预期值时,才会将该位置的值更新为新值,否则不执行任何操作。整个操作是原子的。
3. 详细对比分析
| 特性 | 悲观锁 | 乐观锁 |
|---|---|---|
| 并发假设 | 总是假设会发生冲突 | 假设不会发生冲突 |
| 实现方式 | 基于锁机制,直接阻塞其他线程 | 基于版本控制或CAS操作 |
| 性能影响 | 有锁竞争时,会导致线程阻塞,上下文切换开销大 | 无锁竞争,无需线程切换,但可能有重试开销 |
| 死锁风险 | 有死锁风险,需要考虑锁的获取顺序 | 无死锁风险 |
| 适用场景 | 高并发写操作、数据冲突频繁 | 读多写少、数据冲突较少 |
| 实现复杂度 | 相对简单,依赖系统锁机制 | 相对复杂,需要自己实现冲突检测逻辑 |
4. Java 中的悲观锁实现
4.1 synchronized 关键字
synchronized 是 Java 内置的悲观锁机制,它可以修饰方法或代码块:
// 修饰方法
synchronized void synchronizedMethod() {
// 同步代码
}
// 修饰代码块
synchronized(lockObject) {
// 同步代码
}
4.2 ReentrantLock
ReentrantLock 是 Java 并发包中提供的悲观锁实现,比 synchronized 提供了更多功能:
ReentrantLock lock = new ReentrantLock();
try {
// 获取锁
lock.lock();
// 业务逻辑
} finally {
// 释放锁
lock.unlock();
}
4.3 ReentrantReadWriteLock
ReentrantReadWriteLock 提供了读写分离的锁机制,允许多个线程同时读取,但写入时需要独占:
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
// 获取读锁
rwLock.readLock().lock();
try {
// 读取操作
} finally {
rwLock.readLock().unlock();
}
// 获取写锁
rwLock.writeLock().lock();
try {
// 写入操作
} finally {
rwLock.writeLock().unlock();
}
5. Java 中的乐观锁实现
5.1 原子类 (java.util.concurrent.atomic)
Java 并发包中的原子类,如 AtomicInteger 、 AtomicLong 等,内部使用 CAS 操作实现乐观锁:
AtomicInteger atomicInt = new AtomicInteger(0);
// CAS 操作示例
boolean updated = atomicInt.compareAndSet(0, 1);
// 等效于:
// if (atomicInt.get() == 0) {
// atomicInt.set(1);
// return true;
// } else {
// return false;
// }
5.2 循环重试机制
通常与 CAS 操作配合使用,当 CAS 操作失败时,会进行循环重试:
int expectValue;
int updateValue;
do {
expectValue = sharedValue;
updateValue = calculateNewValue(expectValue);
} while (!atomic.compareAndSet(expectValue, updateValue));
5.3 自定义乐观锁实现
在业务代码中可以实现乐观锁机制,例如:
public class OptimisticLockExample {
private volatile int version = 0;
private String data;
public boolean update(String newData, int expectedVersion) {
if (this.version != expectedVersion) {
return false; // 版本不匹配,更新失败
}
this.data = newData;
this.version++;
return true; // 更新成功
}
}
6. 数据库中的实现
6.1 悲观锁
在数据库中,悲观锁通常通过以下方式实现:
- 共享锁(读锁) :允许其他事务读取但不能修改数据
SELECT * FROM table WHERE id = 1 LOCK IN SHARE MODE; -- MySQL
SELECT * FROM table WHERE id = 1 FOR SHARE; -- PostgreSQL
排他锁(写锁) :不允许其他事务读取或修改数据
SELECT * FROM table WHERE id = 1 FOR UPDATE; -- MySQL/PostgreSQL
6.2 乐观锁
数据库中实现乐观锁的方式:
- 版本号字段 :
-- 表结构
CREATE TABLE product (
id INT PRIMARY KEY,
name VARCHAR(100),
price DECIMAL(10,2),
version INT DEFAULT 0
);
-- 更新语句
UPDATE product
SET price = 100, version = version + 1
WHERE id = 1 AND version = 0;
- 时间戳字段 :
-- 表结构
CREATE TABLE product (
id INT PRIMARY KEY,
name VARCHAR(100),
price DECIMAL(10,2),
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- 更新语句
UPDATE product
SET price = 100
WHERE id = 1 AND update_time = ?;
7. 性能考量
7.1 悲观锁的性能特点
- 优势 :实现简单,无需额外的冲突检测机制
- 劣势 :
- 锁竞争会导致线程阻塞
- 线程上下文切换开销大
- 在高并发场景下,性能下降明显
7.2 乐观锁的性能特点
- 优势 :
- 无锁竞争,线程不会阻塞
- 无需上下文切换,减少系统开销
- 在低冲突环境下,性能明显优于悲观锁
- 劣势 :
- 冲突检测和重试机制增加了代码复杂度
- 在高冲突环境下,重试频繁会导致性能下降
- 无法解决长时间范围的冲突问题
8. 适用场景分析
8.1 选择悲观锁的场景
- 高竞争环境 :多线程同时修改同一数据的频率高
- 长事务 :事务执行时间较长,可能导致乐观锁重试失败概率高
- 一致性要求高 :不允许数据发生冲突,必须保证操作的原子性
- 业务逻辑复杂 :难以实现乐观锁的冲突检测机制
8.2 选择乐观锁的场景
- 读多写少 :数据读取操作远多于修改操作
- 低冲突环境 :多线程并发修改同一数据的概率低
- 高并发场景 :需要提高系统的并发处理能力
- 响应时间敏感 :不希望线程被阻塞
9. 潜在问题与解决方案
9.1 悲观锁的问题 死锁
问题 :多个线程互相持有对方需要的锁,导致无限等待
解决方案 :
- 按固定顺序获取锁
- 设置锁超时时间
- 使用 ReentrantLock 的 tryLock() 方法尝试获取锁 活锁
问题 :线程互相谦让,不断尝试获取锁但都失败
解决方案 :
- 引入随机等待时间
- 优先级策略
9.2 乐观锁的问题 ABA问题
问题 :值从A变成B再变回A,CAS操作无法检测到这个变化
解决方案 :
- 使用版本号或时间戳
- Java中的 AtomicStampedReference 和 AtomicMarkableReference 自旋开销
问题 :高并发场景下,CAS操作失败导致大量自旋,消耗CPU资源
解决方案 :
- 限制重试次数
- 结合其他同步机制(如锁)
- 使用自适应自旋 长时间事务问题
问题 :长事务会导致乐观锁冲突概率增加,频繁重试
解决方案 :
- 将长事务拆分为多个短事务
- 适当增加重试次数
- 考虑使用悲观锁
10. 实际应用案例分析
10.1 电商库存扣减场景 场景分析
- 高并发访问,读多写少
- 库存扣减需要保证准确性 实现方案
方案1:乐观锁
// 库存实体类
class Inventory {
private Long productId;
private Integer stock;
private Integer version;
// getters and setters
}
// 库存扣减方法
public boolean deductStock(Long productId, Integer quantity) {
// 1. 读取库存信息
Inventory inventory = inventoryDao.getByProductId(productId);
// 2. 检查库存是否足够
if (inventory.getStock() < quantity) {
return false;
}
// 3. 乐观锁更新库存
int affectedRows = inventoryDao.updateStock(
productId,
inventory.getStock() - quantity,
inventory.getVersion()
);
return affectedRows > 0;
}
方案2:悲观锁
@Transactional
public boolean deductStockWithLock(Long productId, Integer quantity) {
// 使用悲观锁查询库存
Inventory inventory = inventoryDao.getByProductIdWithLock(productId);
if (inventory.getStock() < quantity) {
return false;
}
// 直接更新库存
inventory.setStock(inventory.getStock() - quantity);
inventoryDao.update(inventory);
return true;
}
10.2 分布式系统中的乐观锁
在分布式系统中实现乐观锁,可以使用分布式事务或消息队列:
// 分布式环境下的乐观锁实现
public boolean processOrderWithDistributedOptimisticLock(Order order) {
int retryCount = 0;
final int MAX_RETRY = 3;
while (retryCount < MAX_RETRY) {
try {
// 1. 获取商品版本信息
Product product = productService.getProductWithVersion(order.getProductId());
// 2. 验证库存
if (product.getStock() < order.getQuantity()) {
return false;
}
// 3. 执行分布式事务,包含扣减库存和创建订单
boolean success = distributedTransactionTemplate.execute(status -> {
// 扣减库存(带版本控制)
boolean inventoryUpdated = inventoryService.updateWithVersion(
order.getProductId(),
product.getStock() - order.getQuantity(),
product.getVersion()
);
if (!inventoryUpdated) {
status.setRollbackOnly();
return false;
}
// 创建订单
orderService.create(order);
return true;
});
if (success) {
return true;
}
retryCount++;
// 指数退避策略
Thread.sleep(100 * (1 << retryCount));
} catch (Exception e) {
log.error("Order processing failed, retry count: {}", retryCount, e);
retryCount++;
}
}
return false;
}
11. 总结与最佳实践
11.1 选择策略建议
-
根据冲突频率选择 :
- 冲突频率高 -> 悲观锁
- 冲突频率低 -> 乐观锁
-
根据系统负载选择 :
- 高并发系统 -> 优先考虑乐观锁
- 低并发系统 -> 两种锁性能差异不大
-
根据业务特点选择 :
- 长事务 -> 悲观锁
- 短事务 -> 乐观锁
11.2 混合使用策略
在复杂系统中,不要局限于单一的锁策略,可以根据不同业务场景混合使用:
- 读写分离 :读操作使用乐观锁,写操作使用悲观锁
- 分级锁策略 :核心数据使用悲观锁,非核心数据使用乐观锁
- 动态调整 :根据系统运行状态动态调整锁策略
11.3 代码实现最佳实践
-
悲观锁最佳实践 :
- 始终在 finally 块中释放锁
- 避免在持有锁的状态下执行耗时操作
- 合理设置锁的粒度,避免过度锁定
-
乐观锁最佳实践 :
- 实现合理的重试机制和退避策略
- 处理好 ABA 问题
- 限制重试次数,避免无限循环
十二、底层原理深度解析
12.1 CAS操作的底层实现原理
12.1.1 CPU指令级别实现
CAS操作的硬件支持:
┌─────────────────────────────────────────────────────────┐
│ CPU原子指令 │
├─────────────────────────────────────────────────────────┤
│ x86架构: │
│ - CMPXCHG (Compare and Exchange) │
│ - LOCK前缀(保证总线锁定) │
│ │
│ ARM架构: │
│ - LDREX/STREX (Load/Store Exclusive) │
│ - 内存独占访问机制 │
│ │
│ 原子性保证: │
│ - 硬件级别的原子操作 │
│ - 缓存一致性协议(MESI) │
│ - 内存屏障(Memory Barrier) │
└─────────────────────────────────────────────────────────┘
CAS操作的汇编实现:
; x86架构下的CAS操作(伪代码)
; 函数签名: bool CAS(int* ptr, int expected, int newValue)
CAS:
mov eax, expected ; 将期望值加载到EAX寄存器
mov edx, newValue ; 将新值加载到EDX寄存器
lock cmpxchg [ptr], edx ; 原子比较并交换
sete al ; 设置结果标志
ret ; 返回结果
; lock前缀的作用:
; 1. 锁定内存总线,防止其他CPU访问
; 2. 保证操作的原子性
; 3. 刷新CPU缓存,保证可见性
12.1.2 CAS操作的完整流程
CAS操作执行流程:
┌─────────────────────────────────────────────────────────┐
│ 步骤1: 读取内存值 │
├─────────────────────────────────────────────────────────┤
│ CPU从内存(或缓存)读取当前值 V │
│ 缓存一致性协议(MESI)保证读取的是最新值 │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ 步骤2: 比较值 │
├─────────────────────────────────────────────────────────┤
│ 比较 V == A(期望值)? │
│ - 如果相等 → 继续步骤3 │
│ - 如果不相等 → 返回false │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ 步骤3: 写入新值(原子操作) │
├─────────────────────────────────────────────────────────┤
│ 使用LOCK前缀锁定内存总线 │
│ 将新值B写入内存位置V │
│ 释放内存总线锁定 │
│ 返回true │
└─────────────────────────────────────────────────────────┘
12.1.3 CAS操作的ABA问题深入分析
ABA问题详解:
时间线:
T1: 线程A读取值 V = 100
T2: 线程B修改值 V = 200
T3: 线程C修改值 V = 100(又变回原值)
T4: 线程A执行CAS(V, 100, 150)
结果: CAS成功(因为V还是100)
问题: 线程A不知道值已经被修改过
ABA问题的危害:
1. 数据结构可能处于不一致状态
2. 链表操作可能导致数据丢失
3. 引用计数可能错误
解决方案1: 版本号机制
- 每次修改都增加版本号
- CAS时同时比较值和版本号
解决方案2: AtomicStampedReference
- Java提供的带版本号的原子引用
- 同时比较引用和版本戳
/**
* ABA问题演示和解决方案
*/
public class ABAProblemDemo {
/**
* 问题演示:ABA问题
*/
public void demonstrateABAProblem() {
AtomicInteger atomicInt = new AtomicInteger(100);
Thread threadA = new Thread(() -> {
int value = atomicInt.get(); // 读取100
// 模拟其他操作
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// CAS操作:期望100,更新为150
boolean success = atomicInt.compareAndSet(value, 150);
System.out.println("Thread A CAS result: " + success);
});
Thread threadB = new Thread(() -> {
// 修改为200
atomicInt.compareAndSet(100, 200);
System.out.println("Thread B updated to: " + atomicInt.get());
});
Thread threadC = new Thread(() -> {
// 又改回100
atomicInt.compareAndSet(200, 100);
System.out.println("Thread C updated to: " + atomicInt.get());
});
threadA.start();
threadB.start();
threadC.start();
// 问题:Thread A的CAS会成功,但不知道值已经被修改过
}
/**
* 解决方案:使用AtomicStampedReference
*/
public void solveABAProblem() {
AtomicStampedReference<Integer> atomicRef =
new AtomicStampedReference<>(100, 0);
Thread threadA = new Thread(() -> {
int[] stampHolder = new int[1];
int value = atomicRef.get(stampHolder); // 读取值和版本号
int oldStamp = stampHolder[0];
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// CAS操作:同时比较值和版本号
boolean success = atomicRef.compareAndSet(
value, 150,
oldStamp, oldStamp + 1
);
System.out.println("Thread A CAS result: " + success);
});
Thread threadB = new Thread(() -> {
int[] stampHolder = new int[1];
int value = atomicRef.get(stampHolder);
// 修改值,版本号自动增加
atomicRef.compareAndSet(value, 200, stampHolder[0], stampHolder[0] + 1);
});
Thread threadC = new Thread(() -> {
int[] stampHolder = new int[1];
int value = atomicRef.get(stampHolder);
// 改回原值,但版本号已经改变
atomicRef.compareAndSet(value, 100, stampHolder[0], stampHolder[0] + 1);
});
threadA.start();
threadB.start();
threadC.start();
// 结果:Thread A的CAS会失败,因为版本号不匹配
}
}
12.2 synchronized底层实现原理
12.2.1 Java对象头结构
Java对象内存布局:
┌─────────────────────────────────────────────────────┐
│ 对象头(Object Header) │
├─────────────────────────────────────────────────────┤
│ Mark Word (64位): │
│ - 哈希码(HashCode) │
│ - GC分代年龄 │
│ - 锁状态标志 │
│ - 偏向锁线程ID │
│ - 轻量级锁指针 │
│ - 重量级锁指针 │
│ │
│ Class Metadata Pointer (64位): │
│ - 指向类的元数据指针 │
│ │
│ Array Length (可选,仅数组对象): │
│ - 数组长度 │
└─────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────┐
│ 实例数据(Instance Data) │
├────────────────────────────────────────────────────┤
│ 对象的实际字段数据 │
└────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ 对齐填充(Padding) │
├─────────────────────────────────────────────────────┤
│ 保证对象大小是8字节的倍数 │
└─────────────────────────────────────────────────────┘
Mark Word的详细结构:
64位JVM下Mark Word结构(不同锁状态):
┌─────────────────────────────────────────────────────┐
│ 无锁状态(Normal) │
├─────────────────────────────────────────────────────┤
│ 25位: 未使用 │
│ 31位: 哈希码 │
│ 1位: 未使用 │
│ 4位: GC分代年龄 │
│ 1位: 偏向锁标志(0) │
│ 2位: 锁标志位(01) │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ 偏向锁状态(Biased) │
├────────────────────────────────────── ──────────────┤
│ 54位: 线程ID │
│ 2位: Epoch │
│ 1位: 未使用 │
│ 4位: GC分代年龄 │
│ 1位: 偏向锁标志(1) │
│ 2位: 锁标志位(01) │
└─────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────┐
│ 轻量级锁状态(Lightweight) │
├───────────────────────────────────────────────────┤
│ 62位: 指向栈中锁记录的指针 │
│ 2位: 锁标志位(00) │
└───────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────┐
│ 重量级锁状态(Heavyweight) │
├───────────────────────────────────────────────────┤
│ 62位: 指向Monitor对象的指针 │
│ 2位: 锁标志位(10) │
└───────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────┐
│ GC标记状态(Marked) │
├───────────────────────────────────────────────────┤
│ 62位: GC标记信息 │
│ 2位: 锁标志位(11) │
└───────────────────────────────────────────────────┘
12.2.2 锁升级过程详解
synchronized锁升级过程:
┌───────────────────────────────────────────────────┐
│ 阶段1: 无锁状态 │
├───────────────────────────────────────────────────┤
│ 对象刚创建,没有任何线程持有锁 │
│ Mark Word: 锁标志位 = 01(无锁) │
└───────────────────────────────────────────────────┘
│
▼ (第一个线程访问)
┌───────────────────────────────────────────────────┐
│ 阶段2: 偏向锁 │
├───────────────────────────────────────────────────┤
│ 第一个线程访问,升级为偏向锁 │
│ Mark Word: 存储线程ID,锁标志位 = 01(偏向锁) │
│ │
│ 优势: │
│ ✅ 同一线程再次访问时无需CAS操作 │
│ ✅ 性能最优(几乎无开销) │
│ │
│ 触发条件: │
│ - 第一个线程访问 │
│ - 偏向锁开启(默认开启) │
└───────────────────────────────────────────────────┘
│
▼ (第二个线程竞争)
┌───────────────────────────────────────────────────┐
│ 阶段3: 轻量级锁 │
├───────────────────────────────────────────────────┤
│ 第二个线程竞争,升级为轻量级锁 │
│ Mark Word: 指向栈中锁记录,锁标志位 = 00(轻量级锁)│
│ │
│ 实现方式: │
│ 1. 在栈帧中创建锁记录(Lock Record) │
│ 2. 将Mark Word复制到锁记录 │
│ 3. 使用CAS将Mark Word指向锁记录 │
│ 4. 如果CAS成功 → 获取轻量级锁 │
│ 5. 如果CAS失败 → 自旋等待或升级为重量级锁 │
│ │
│ 优势: │
│ ✅ 线程不会阻塞(自旋等待) │
│ ✅ 适合锁竞争不激烈的场景 │
└───────────────────────────────────────────────────┘
│
▼ (自旋失败或竞争激烈)
┌───────────────────────────────────────────────────┐
│ 阶段4: 重量级锁 │
├───────────────────────────────────────────────────┤
│ 竞争激烈或自旋失败,升级为重量级锁 │
│ Mark Word: 指向Monitor对象,锁标志位 = 10(重量级锁)│
│ │
│ Monitor对象结构: │
│ - Owner: 持有锁的线程 │
│ - WaitSet: 等待队列(wait()的线程) │
│ - EntryList: 阻塞队列(竞争锁的线程) │
│ - Recursions: 重入次数 │
│ │
│ 实现方式: │
│ 1. 创建Monitor对象 │
│ 2. 将Mark Word指向Monitor对象 │
│ 3. 线程进入EntryList等待 │
│ 4. 操作系统级别的阻塞(Mutex) │
│ │
│ 劣势: │
│ ❌ 线程阻塞,上下文切换开销大 │
│ ❌ 性能较低 │
└──────────────────────────────────────────────────┘
12.2.3 Monitor对象深入解析
/**
* Monitor对象结构(简化版)
*
* 注意:这是概念模型,实际实现是C++代码
*/
public class Monitor {
/**
* 持有锁的线程
*/
private Thread owner;
/**
* 重入次数
*/
private int recursions;
/**
* 等待队列(调用wait()的线程)
*/
private WaitSet waitSet;
/**
* 阻塞队列(竞争锁的线程)
*/
private EntryList entryList;
/**
* 获取锁
*/
public void enter() {
Thread currentThread = Thread.currentThread();
if (owner == currentThread) {
// 重入:增加计数
recursions++;
return;
}
// 尝试快速获取锁
if (compareAndSetOwner(null, currentThread)) {
// 获取锁成功
return;
}
// 获取锁失败,进入阻塞队列
entryList.add(currentThread);
// 操作系统级别的阻塞(park)
park(currentThread);
}
/**
* 释放锁
*/
public void exit() {
Thread currentThread = Thread.currentThread();
if (owner != currentThread) {
throw new IllegalMonitorStateException();
}
if (recursions > 0) {
// 重入:减少计数
recursions--;
return;
}
// 释放锁
owner = null;
// 唤醒阻塞队列中的线程
Thread nextThread = entryList.remove();
if (nextThread != null) {
unpark(nextThread);
}
}
/**
* 等待(释放锁,进入等待队列)
*/
public void wait() {
Thread currentThread = Thread.currentThread();
if (owner != currentThread) {
throw new IllegalMonitorStateException();
}
// 保存重入次数
int savedRecursions = recursions;
owner = null;
recursions = 0;
// 进入等待队列
waitSet.add(currentThread);
// 唤醒阻塞队列中的线程
Thread nextThread = entryList.remove();
if (nextThread != null) {
unpark(nextThread);
}
// 阻塞当前线程
park(currentThread);
// 被唤醒后,重新获取锁
enter();
recursions = savedRecursions;
}
/**
* 通知(唤醒等待队列中的线程)
*/
public void notify() {
Thread currentThread = Thread.currentThread();
if (owner != currentThread) {
throw new IllegalMonitorStateException();
}
// 从等待队列中移除一个线程
Thread waitingThread = waitSet.remove();
if (waitingThread != null) {
// 移动到阻塞队列
entryList.add(waitingThread);
}
}
private boolean compareAndSetOwner(Thread expect, Thread update) {
// CAS操作(实际是C++实现)
return false;
}
private void park(Thread thread) {
// 操作系统级别的阻塞(实际是C++实现)
}
private void unpark(Thread thread) {
// 唤醒线程(实际是C++实现)
}
}
12.3 AQS(AbstractQueuedSynchronizer)原理深度解析
12.3.1 AQS核心数据结构
AQS数据结构:
┌────────────────────────────────────────────────────┐
│ AQS核心组件 │
├────────────────────────────────────────────────────┤
│ │
│ state (volatile int): │
│ - 同步状态(锁的状态) │
│ - 使用CAS操作修改 │
│ │
│ head (Node): │
│ - 同步队列头节点 │
│ │
│ tail (Node): │
│ - 同步队列尾节点 │
│ │
│ Node节点结构: │
│ - prev: 前驱节点 │
│ - next: 后继节点 │
│ - thread: 等待的线程 │
│ - waitStatus: 等待状态 │
│ - CANCELLED(1): 已取消 │
│ - SIGNAL(-1): 需要唤醒后继节点 │
│ - CONDITION(-2): 在条件队列中 │
│ - PROPAGATE(-3): 共享模式传播 │
└────────────────────────────────────────────────────┘
12.3.2 AQS获取锁流程
/**
* AQS获取锁流程(简化版)
*/
public abstract class AbstractQueuedSynchronizer {
/**
* 获取锁(模板方法)
*/
public final void acquire(int arg) {
// 1. 尝试获取锁(由子类实现)
if (!tryAcquire(arg) &&
// 2. 获取失败,加入同步队列
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {
// 3. 如果被中断,恢复中断状态
selfInterrupt();
}
}
/**
* 尝试获取锁(由子类实现)
*/
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
/**
* 加入同步队列
*/
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null) {
node.prev = pred;
// CAS设置尾节点
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 如果CAS失败,使用enq方法
enq(node);
return node;
}
/**
* 在队列中获取锁(自旋)
*/
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
// 如果前驱节点是头节点,尝试获取锁
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// 检查是否需要阻塞
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt()) {
interrupted = true;
}
}
} finally {
if (failed) {
cancelAcquire(node);
}
}
}
/**
* 检查是否需要阻塞
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL) {
// 前驱节点需要唤醒后继节点,可以安全阻塞
return true;
}
if (ws > 0) {
// 前驱节点已取消,跳过
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 设置前驱节点为SIGNAL状态
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
/**
* 阻塞并检查中断
*/
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
}
12.3.3 ReentrantLock基于AQS的实现
/**
* ReentrantLock基于AQS的实现原理
*/
public class ReentrantLock implements Lock {
private final Sync sync;
/**
* 同步器(继承AQS)
*/
abstract static class Sync extends AbstractQueuedSynchronizer {
/**
* 尝试获取锁(非公平锁实现)
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 锁未被持有,尝试获取
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
} else if (current == getExclusiveOwnerThread()) {
// 重入:增加计数
int nextc = c + acquires;
if (nextc < 0) {
throw new Error("Maximum lock count exceeded");
}
setState(nextc);
return true;
}
return false;
}
/**
* 尝试释放锁
*/
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread()) {
throw new IllegalMonitorStateException();
}
boolean free = false;
if (c == 0) {
// 完全释放锁
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
}
/**
* 非公平锁
*/
static final class NonfairSync extends Sync {
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
/**
* 公平锁
*/
static final class FairSync extends Sync {
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 检查是否有前驱节点(公平锁)
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
} else if (current == getExclusiveOwnerThread()) {
// 重入
int nextc = c + acquires;
if (nextc < 0) {
throw new Error("Maximum lock count exceeded");
}
setState(nextc);
return true;
}
return false;
}
}
}
12.4 内存可见性与Happens-Before关系
12.4.1 Java内存模型(JMM)
Java内存模型(JMM):
┌─────────────────────────────────────────────────┐
│ 线程本地内存 │
├─────────────────────────────────────────────────┤
│ Thread-1: │
│ - 本地变量副本 │
│ - 工作内存 │
│ │
│ Thread-2: │
│ - 本地变量副本 │
│ - 工作内存 │
└─────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────┐
│ 主内存(共享内存) │
├────────────────────────────────────────────────┤
│ - 共享变量 │
│ - 对象的实际数据 │
└────────────────────────────────────────────────┘
内存可见性问题:
问题场景:
Thread-1:
1. 从主内存读取变量x = 0
2. 在工作内存中修改x = 1
3. 写回主内存(可能延迟)
Thread-2:
1. 从主内存读取变量x = 0(可能读到旧值)
2. 基于旧值进行操作(数据不一致)
解决方案:
1. volatile关键字(保证可见性)
2. synchronized(保证可见性和原子性)
3. final关键字(初始化后不可变)
4. Happens-Before规则
12.4.2 Happens-Before规则详解
Happens-Before规则(JMM核心规则):
规则1: 程序顺序规则
- 同一线程中,前面的操作happens-before后面的操作
- 保证单线程内的执行顺序
规则2: 监视器锁规则
- 解锁操作happens-before后续的加锁操作
- synchronized保证可见性
规则3: volatile变量规则
- volatile写操作happens-before后续的volatile读操作
- 保证volatile变量的可见性
规则4: 传递性规则
- 如果A happens-before B,B happens-before C
- 那么A happens-before C
规则5: start()规则
- 线程start()操作happens-before该线程的任何操作
规则6: join()规则
- 线程的所有操作happens-before其他线程调用join()返回
规则7: 中断规则
- 对线程interrupt()的调用happens-before被中断线程检测到中断
/**
* Happens-Before规则示例
*/
public class HappensBeforeDemo {
private int x = 0;
private volatile boolean flag = false;
/**
* 规则1: 程序顺序规则
*/
public void programOrderRule() {
int a = 1; // 操作1
int b = 2; // 操作2(happens-after操作1)
int c = a + b; // 操作3(happens-after操作2)
}
/**
* 规则2: 监视器锁规则
*/
public void monitorLockRule() {
synchronized (this) {
x = 1; // 操作1:解锁happens-before后续加锁
}
synchronized (this) {
int value = x; // 操作2:能看到操作1的修改
}
}
/**
* 规则3: volatile变量规则
*/
public void volatileRule() {
// 线程1
new Thread(() -> {
x = 1; // 操作1
flag = true; // 操作2:volatile写
}).start();
// 线程2
new Thread(() -> {
if (flag) { // 操作3:volatile读(happens-after操作2)
int value = x; // 操作4:能看到操作1的修改
}
}).start();
}
/**
* 规则4: 传递性规则
*/
public void transitivityRule() {
// 操作A
synchronized (this) {
x = 1; // A happens-before B(规则2)
}
// 操作B
flag = true; // B happens-before C(规则3)
// 操作C
new Thread(() -> {
if (flag) { // C能看到B的修改
int value = x; // C能看到A的修改(传递性)
}
}).start();
}
}
12.5 锁的性能测试与基准测试
12.5.1 性能测试代码
/**
* 锁性能基准测试
*/
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Benchmark)
public class LockPerformanceBenchmark {
private int counter = 0;
private final Object lock = new Object();
private final ReentrantLock reentrantLock = new ReentrantLock();
private final AtomicInteger atomicCounter = new AtomicInteger(0);
private volatile int volatileCounter = 0;
/**
* 基准测试1: synchronized性能
*/
@Benchmark
public void testSynchronized() {
synchronized (lock) {
counter++;
}
}
/**
* 基准测试2: ReentrantLock性能
*/
@Benchmark
public void testReentrantLock() {
reentrantLock.lock();
try {
counter++;
} finally {
reentrantLock.unlock();
}
}
/**
* 基准测试3: CAS操作性能
*/
@Benchmark
public void testCAS() {
atomicCounter.incrementAndGet();
}
/**
* 基准测试4: volatile + CAS性能
*/
@Benchmark
public void testVolatileCAS() {
// 使用CAS更新volatile变量
int current;
do {
current = volatileCounter;
} while (!compareAndSetVolatile(current, current + 1));
}
private boolean compareAndSetVolatile(int expect, int update) {
// 使用Unsafe实现CAS(实际项目中不推荐直接使用)
return false;
}
}
12.5.2 性能测试结果分析
性能测试结果(单线程场景):
┌────────────────────────────────────────────────────────┐
│ 操作耗时(纳秒) │
├──────────────┬──────────┬──────────┬───────────────────┤
│ 锁类型 │ 无竞争 │ 低竞争 │ 高竞争 │
├──────────────┼──────────┼──────────┼───────────────────┤
│ synchronized │ ~10ns │ ~50ns │ ~500ns │
│ ReentrantLock│ ~15ns │ ~60ns │ ~600ns │
│ CAS │ ~5ns │ ~20ns │ ~200ns(自旋) │
│ volatile+CAS │ ~8ns │ ~30ns │ ~300ns(自旋) │
└──────────────┴──────────┴──────────┴───────────────────┘
性能分析:
1. 无竞争场景:
- CAS最快(~5ns),无锁操作
- synchronized次之(~10ns),偏向锁优化
- ReentrantLock较慢(~15ns),需要方法调用开销
2. 低竞争场景:
- CAS仍然最快(~20ns),自旋等待
- synchronized中等(~50ns),轻量级锁
- ReentrantLock较慢(~60ns),需要方法调用
3. 高竞争场景:
- CAS性能下降(~200ns),大量自旋消耗CPU
- synchronized性能下降(~500ns),升级为重量级锁
- ReentrantLock性能下降(~600ns),线程阻塞
结论:
- 无竞争或低竞争 → CAS最优
- 高竞争 → 需要权衡(CAS自旋 vs 锁阻塞)
12.6 分布式环境下的悲观锁与乐观锁
12.6.1 分布式悲观锁实现
/**
* 分布式悲观锁实现(基于Redis)
*/
@Service
@Slf4j
public class DistributedPessimisticLock {
@Autowired
private RedissonClient redissonClient;
/**
* 获取分布式悲观锁
*/
public <T> T executeWithLock(String lockKey, long waitTime, long leaseTime,
Supplier<T> supplier) {
RLock lock = redissonClient.getLock(lockKey);
try {
// 尝试获取锁(阻塞等待)
boolean acquired = lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
if (!acquired) {
throw new LockAcquisitionException("Failed to acquire lock: " + lockKey);
}
return supplier.get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new LockAcquisitionException("Interrupted", e);
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
12.6.2 分布式乐观锁实现
/**
* 分布式乐观锁实现(基于数据库版本号)
*/
@Service
@Slf4j
public class DistributedOptimisticLock {
@Autowired
private InventoryMapper inventoryMapper;
/**
* 分布式乐观锁更新库存
*/
public boolean updateInventoryWithOptimisticLock(Long productId, Integer quantity) {
int maxRetries = 3;
int retryCount = 0;
while (retryCount < maxRetries) {
// 1. 读取当前版本
Inventory inventory = inventoryMapper.selectById(productId);
if (inventory == null) {
return false;
}
int oldVersion = inventory.getVersion();
int newStock = inventory.getStock() - quantity;
if (newStock < 0) {
return false; // 库存不足
}
// 2. 乐观锁更新(版本号匹配才更新)
int affectedRows = inventoryMapper.updateWithVersion(
productId,
newStock,
oldVersion
);
if (affectedRows > 0) {
// 更新成功
return true;
}
// 3. 更新失败(版本冲突),重试
retryCount++;
// 指数退避
try {
Thread.sleep(10 * (1 << retryCount));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
return false; // 重试失败
}
}
12.7 锁优化技巧与高级特性
12.7.1 锁粗化(Lock Coarsening)
/**
* 锁粗化优化
*/
public class LockCoarsening {
private final Object lock = new Object();
/**
* ❌ 错误:多次加锁解锁(JVM会自动优化为锁粗化)
*/
public void badExample() {
synchronized (lock) {
doSomething1();
}
synchronized (lock) {
doSomething2();
}
synchronized (lock) {
doSomething3();
}
}
/**
* ✅ 正确:一次加锁解锁(手动锁粗化)
*/
public void goodExample() {
synchronized (lock) {
doSomething1();
doSomething2();
doSomething3();
}
}
/**
* JVM自动锁粗化条件:
* 1. 连续的synchronized块
* 2. 使用同一个锁对象
* 3. 中间没有其他操作
*/
}
12.7.2 锁消除(Lock Elimination)
/**
* 锁消除优化
*/
public class LockElimination {
/**
* 示例:StringBuffer的append方法
*
* 在单线程环境下,JVM会消除StringBuffer的锁
*/
public String concatenateStrings(String[] strings) {
StringBuffer sb = new StringBuffer();
for (String str : strings) {
sb.append(str); // append方法内部有synchronized
}
return sb.toString();
}
/**
* JVM锁消除条件:
* 1. 对象不会逃逸出方法
* 2. 没有其他线程访问该对象
* 3. 逃逸分析(Escape Analysis)证明对象是线程局部的
*/
}
12.7.3 自适应自旋(Adaptive Spinning)
自适应自旋机制:
┌─────────────────────────────────────────────────────┐
│ 自旋策略 │
├─────────────────────────────────────────────────────┤
│ 1. 初始自旋次数: 10次(JVM默认) │
│ │
│ 2. 自旋成功: │
│ - 增加自旋次数 │
│ - 说明锁竞争不激烈,自旋有效 │
│ │
│ 3. 自旋失败: │
│ - 减少自旋次数 │
│ - 说明锁竞争激烈,自旋浪费CPU │
│ │
│ 4. 动态调整: │
│ - 根据历史成功率调整 │
│ - 平衡CPU消耗和响应时间 │
└─────────────────────────────────────────────────────┘
12.7.4 读写锁优化
/**
* 读写锁优化策略
*/
@Service
@Slf4j
public class ReadWriteLockOptimization {
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private final ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
private final ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
private Map<String, String> cache = new HashMap<>();
/**
* 读操作(使用读锁,允许多线程并发读取)
*/
public String read(String key) {
readLock.lock();
try {
return cache.get(key);
} finally {
readLock.unlock();
}
}
/**
* 写操作(使用写锁,独占访问)
*/
public void write(String key, String value) {
writeLock.lock();
try {
cache.put(key, value);
} finally {
writeLock.unlock();
}
}
/**
* 优化:读多写少场景下的性能提升
*
* 性能对比:
* - 使用synchronized: 所有操作串行,QPS = 1000
* - 使用读写锁: 读操作并发,QPS = 5000(假设读:写 = 10:1)
*/
}
12.8 实际场景深度分析
12.8.1 高并发秒杀场景
/**
* 高并发秒杀场景优化
*/
@Service
@Slf4j
public class SeckillService {
@Autowired
private InventoryMapper inventoryMapper;
@Autowired
private RedissonClient redissonClient;
/**
* 方案1: 悲观锁(简单但性能较低)
*/
@Transactional
public boolean seckillWithPessimisticLock(Long productId, Integer quantity) {
// 使用数据库行锁
Inventory inventory = inventoryMapper.selectByIdForUpdate(productId);
if (inventory.getStock() < quantity) {
return false;
}
inventory.setStock(inventory.getStock() - quantity);
inventoryMapper.updateById(inventory);
return true;
}
/**
* 方案2: 乐观锁(性能较高,但需要重试)
*/
public boolean seckillWithOptimisticLock(Long productId, Integer quantity) {
int maxRetries = 3;
for (int i = 0; i < maxRetries; i++) {
Inventory inventory = inventoryMapper.selectById(productId);
if (inventory.getStock() < quantity) {
return false;
}
int affectedRows = inventoryMapper.updateWithVersion(
productId,
inventory.getStock() - quantity,
inventory.getVersion()
);
if (affectedRows > 0) {
return true;
}
// 重试
try {
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
return false;
}
/**
* 方案3: Redis分布式锁 + 数据库乐观锁(推荐)
*/
public boolean seckillWithDistributedLock(Long productId, Integer quantity) {
String lockKey = "seckill:product:" + productId;
RLock lock = redissonClient.getLock(lockKey);
try {
// 快速失败,不等待
if (!lock.tryLock(0, 10, TimeUnit.SECONDS)) {
return false;
}
// 使用乐观锁更新数据库
return seckillWithOptimisticLock(productId, quantity);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
/**
* 方案4: Redis原子操作(最优性能)
*/
public boolean seckillWithRedisAtomic(Long productId, Integer quantity) {
String stockKey = "stock:product:" + productId;
// 使用Lua脚本保证原子性
String script =
"local stock = redis.call('get', KEYS[1]) " +
"if stock and tonumber(stock) >= tonumber(ARGV[1]) then " +
" return redis.call('decrby', KEYS[1], ARGV[1]) " +
"else " +
" return -1 " +
"end";
Long result = redissonClient.getScript().eval(
script,
RScript.ReturnType.INTEGER,
Collections.singletonList(stockKey),
quantity
);
return result != null && result >= 0;
}
}
12.8.2 分布式事务场景
/**
* 分布式事务中的锁使用
*/
@Service
@Slf4j
public class DistributedTransactionService {
@Autowired
private AccountMapper accountMapper;
@Autowired
private RedissonClient redissonClient;
/**
* 分布式转账(使用分布式锁保证一致性)
*/
@Transactional
public boolean transfer(Long fromAccountId, Long toAccountId, BigDecimal amount) {
// 1. 获取两个账户的锁(按ID排序,防止死锁)
List<Long> accountIds = Arrays.asList(fromAccountId, toAccountId);
Collections.sort(accountIds);
List<RLock> locks = accountIds.stream()
.map(id -> redissonClient.getLock("account:lock:" + id))
.collect(Collectors.toList());
try {
// 2. 按顺序获取锁
for (RLock lock : locks) {
if (!lock.tryLock(5, 30, TimeUnit.SECONDS)) {
throw new LockAcquisitionException("Failed to acquire lock");
}
}
// 3. 执行转账操作
Account fromAccount = accountMapper.selectById(fromAccountId);
Account toAccount = accountMapper.selectById(toAccountId);
if (fromAccount.getBalance().compareTo(amount) < 0) {
return false;
}
fromAccount.setBalance(fromAccount.getBalance().subtract(amount));
toAccount.setBalance(toAccount.getBalance().add(amount));
accountMapper.updateById(fromAccount);
accountMapper.updateById(toAccount);
return true;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
} finally {
// 4. 按逆序释放锁
Collections.reverse(locks);
for (RLock lock : locks) {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
}
十三、高级主题与前沿技术
13.1 无锁编程(Lock-Free Programming)
无锁编程概念:
┌────────────────────────────────────────────────┐
│ 无锁数据结构 │
├────────────────────────────────────────────────┤
│ 特点: │
│ - 不使用传统的锁机制 │
│ - 使用CAS操作实现并发控制 │
│ - 线程不会阻塞 │
│ │
│ 优势: │
│ ✅ 高性能(无阻塞) │
│ ✅ 无死锁风险 │
│ ✅ 可扩展性好 │
│ │
│ 劣势: │
│ ❌ 实现复杂 │
│ ❌ 可能出现ABA问题 │
│ ❌ CPU消耗较高(自旋) │
└────────────────────────────────────────────────┘
/**
* 无锁栈实现(Lock-Free Stack)
*/
public class LockFreeStack<T> {
private static class Node<T> {
final T value;
volatile Node<T> next;
Node(T value) {
this.value = value;
}
}
private volatile Node<T> head = null;
/**
* 无锁push操作
*/
public void push(T value) {
Node<T> newNode = new Node<>(value);
Node<T> currentHead;
do {
currentHead = head;
newNode.next = currentHead;
} while (!compareAndSetHead(currentHead, newNode));
}
/**
* 无锁pop操作
*/
public T pop() {
Node<T> currentHead;
Node<T> nextHead;
do {
currentHead = head;
if (currentHead == null) {
return null;
}
nextHead = currentHead.next;
} while (!compareAndSetHead(currentHead, nextHead));
return currentHead.value;
}
private boolean compareAndSetHead(Node<T> expect, Node<T> update) {
// 使用Unsafe实现CAS(实际项目中应使用AtomicReference)
return false;
}
}
13.2 分段锁(Striped Lock)
/**
* 分段锁实现(减少锁竞争)
*/
public class StripedLock {
private final int stripes;
private final ReentrantLock[] locks;
public StripedLock(int stripes) {
this.stripes = stripes;
this.locks = new ReentrantLock[stripes];
for (int i = 0; i < stripes; i++) {
locks[i] = new ReentrantLock();
}
}
/**
* 根据key获取对应的锁
*/
public ReentrantLock getLock(Object key) {
int hash = key.hashCode();
int index = Math.abs(hash % stripes);
return locks[index];
}
/**
* 使用分段锁保护操作
*/
public <T> T executeWithLock(Object key, Supplier<T> supplier) {
ReentrantLock lock = getLock(key);
lock.lock();
try {
return supplier.get();
} finally {
lock.unlock();
}
}
/**
* 性能优势:
* - 不同key的操作可以并发执行
* - 减少锁竞争
* - 提高并发性能
*/
}
13.3 锁的性能监控
/**
* 锁性能监控
*/
@Component
@Slf4j
public class LockPerformanceMonitor {
@Autowired
private MeterRegistry meterRegistry;
private final Map<String, LockMetrics> lockMetrics = new ConcurrentHashMap<>();
/**
* 监控锁获取时间
*/
public <T> T monitorLock(String lockName, Supplier<T> supplier) {
long startTime = System.nanoTime();
boolean acquired = false;
try {
T result = supplier.get();
acquired = true;
return result;
} finally {
long duration = System.nanoTime() - startTime;
recordLockMetrics(lockName, acquired, duration);
}
}
/**
* 记录锁指标
*/
private void recordLockMetrics(String lockName, boolean acquired, long durationNs) {
LockMetrics metrics = lockMetrics.computeIfAbsent(
lockName,
k -> new LockMetrics()
);
metrics.totalAttempts++;
if (acquired) {
metrics.successCount++;
metrics.totalAcquireTime += durationNs;
} else {
metrics.failureCount++;
}
// 记录Prometheus指标
meterRegistry.timer("lock.acquire.duration", "lock", lockName)
.record(durationNs, TimeUnit.NANOSECONDS);
meterRegistry.counter("lock.acquire.total", "lock", lockName, "status", acquired ? "success" : "failure")
.increment();
}
/**
* 获取锁统计信息
*/
public LockStatistics getStatistics(String lockName) {
LockMetrics metrics = lockMetrics.get(lockName);
if (metrics == null) {
return null;
}
return LockStatistics.builder()
.lockName(lockName)
.totalAttempts(metrics.totalAttempts)
.successCount(metrics.successCount)
.failureCount(metrics.failureCount)
.successRate(metrics.totalAttempts > 0 ?
(double) metrics.successCount / metrics.totalAttempts : 0)
.averageAcquireTime(metrics.successCount > 0 ?
metrics.totalAcquireTime / metrics.successCount : 0)
.build();
}
@Data
private static class LockMetrics {
private long totalAttempts = 0;
private long successCount = 0;
private long failureCount = 0;
private long totalAcquireTime = 0;
}
@Data
@Builder
public static class LockStatistics {
private String lockName;
private long totalAttempts;
private long successCount;
private long failureCount;
private double successRate;
private long averageAcquireTime;
}
}
十四、总结与最佳实践
14.1 选择决策树
锁选择决策树:
开始
│
├─ 是否需要跨进程/跨服务器?
│ ├─ 是 → 使用分布式锁(Redis/ZooKeeper/etcd)
│ └─ 否 → 继续
│
├─ 数据冲突频率如何?
│ ├─ 高冲突 → 使用悲观锁(synchronized/ReentrantLock)
│ └─ 低冲突 → 继续
│
├─ 读多写少?
│ ├─ 是 → 使用读写锁(ReentrantReadWriteLock)
│ └─ 否 → 继续
│
├─ 需要公平性?
│ ├─ 是 → 使用公平锁(FairSync)
│ └─ 否 → 使用非公平锁(NonfairSync)
│
└─ 性能要求极高?
├─ 是 → 使用CAS/乐观锁
└─ 否 → 使用悲观锁
14.2 性能优化建议
性能优化建议:
1. 减少锁的持有时间
✅ 只锁必要的代码块
❌ 不要在锁内执行耗时操作(IO、网络请求)
2. 减小锁的粒度
✅ 使用细粒度锁(行锁)
❌ 避免粗粒度锁(表锁)
3. 减少锁的竞争
✅ 使用分段锁
✅ 使用读写锁(读多写少)
✅ 使用无锁数据结构
4. 避免死锁
✅ 统一锁的获取顺序
✅ 设置锁超时时间
✅ 使用tryLock()而非lock()
5. 合理使用锁升级
✅ 让JVM自动优化(偏向锁→轻量级锁→重量级锁)
❌ 不要强制使用重量级锁
14.3 常见陷阱与避免方法
常见陷阱:
1. 锁的粒度不当
❌ 锁整个方法
✅ 只锁必要的代码块
2. 死锁
❌ 多个锁的获取顺序不一致
✅ 统一锁的获取顺序
3. 活锁
❌ 线程互相谦让
✅ 引入随机等待时间
4. 锁泄漏
❌ 异常时未释放锁
✅ 使用try-finally确保释放
5. 过度使用锁
❌ 所有操作都加锁
✅ 只在必要时加锁
6. 忽略ABA问题
❌ 直接使用CAS
✅ 使用版本号或AtomicStampedReference
参考资源:
827

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



