高并发下的锁选择:乐观锁 vs 悲观锁全面对比
目录
1. 乐观锁 (Optimistic Lock)
核心思想
实现机制
代码示例
优缺点
适用场景
2. 悲观锁 (Pessimistic Lock)
核心思想
实现机制
代码示例
优缺点
适用场景
3. 公平锁 (Fair Lock) vs 非公平锁 (Non-fair Lock)
公平锁
非公平锁
对比表格
4. 综合对比
1. 乐观锁 (Optimistic Lock)
核心思想
先操作,后检查冲突。假设并发冲突的概率很低,允许多个线程同时访问资源,只在提交时检查是否有冲突。
实现机制
-
版本号/时间戳:为数据增加一个版本字段
-
CAS操作:Compare-And-Swap,比较并交换
-
流程:读取数据 → 修改数据 → 验证版本号 → 如果验证通过则提交,否则重试或失败
代码示例
UPDATE products
SET stock = stock - 1, version = version + 1
WHERE id = 100 AND version = 5;
// Java CAS示例
AtomicInteger atomicInt = new AtomicInteger(0);
atomicInt.compareAndSet(0, 1); // 如果当前值是0,则设置为1
优缺点
优点:
-
并发性能高,减少线程阻塞
-
适合读多写少的场景
缺点:
-
冲突时需要重试,可能产生自旋开销
-
不保证操作一定成功
适用场景
-
数据库并发更新
-
购物车库存扣减
-
社交媒体点赞功能
2. 悲观锁 (Pessimistic Lock)
核心思想
先加锁,后操作。假设并发冲突的概率很高,每次操作前先获取锁,确保独占访问。
实现机制
-
synchronized:Java内置锁
-
ReentrantLock:可重入锁
-
数据库行锁:SELECT ... FOR UPDATE
代码示例
public synchronized void criticalSection() {// 线程安全代码
}// Java ReentrantLock
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {// 线程安全代码
} finally {lock.unlock();
}
START TRANSACTION;
SELECT * FROM accounts WHERE id = 1 FOR UPDATE;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
COMMIT;
优缺点
优点:
-
保证数据强一致性
-
编程模型简单
缺点:
-
并发性能较低,线程阻塞
-
可能产生死锁
适用场景
-
银行转账交易
-
订单支付处理
-
需要强一致性的金融系统
3. 公平锁 (Fair Lock) vs 非公平锁 (Non-fair Lock)
公平锁
按照请求顺序分配锁,先到先得。
ReentrantLock fairLock = new ReentrantLock(true); // true表示公平锁
特点:
-
线程按申请顺序获取锁
-
不会出现线程饥饿现象
-
性能相对较低(需要维护队列)
非公平锁
允许插队,后申请的线程可能先获取到锁。
ReentrantLock nonFairLock = new ReentrantLock(); // 默认非公平锁
ReentrantLock nonFairLock = new ReentrantLock(false);
特点:
-
吞吐量更高,减少线程切换
-
可能造成线程饥饿
-
性能更好
对比表格
特性 | 公平锁 | 非公平锁 |
---|---|---|
获取顺序 | 先进先出 | 允许插队 |
性能 | 较低 | 较高 |
线程饥饿 | 不会 | 可能 |
实现复杂度 | 较高 | 较低 |
4. 综合对比
维度 | 乐观锁 | 悲观锁 |
---|---|---|
并发性 | 高 | 低 |
开销 | 冲突时开销大 | 始终有开销 |
数据一致性 | 最终一致性 | 强一致性 |
适用场景 | 读多写少 | 写多读少 |
实现方式 | 版本号、CAS | synchronized、ReentrantLock |