“悲观锁 和 乐观锁”是并发控制领域非常重要的两个核心概念,它们是两种处理多线程共享资源冲突的策略,广泛应用于数据库、操作系统、并发容器、甚至用户态内存管理中。
✅ 简单直观对比
类型 | 思维方式 | 代表手段 | 特点 |
---|---|---|---|
悲观锁 | “别人肯定会改,我要先锁!” | mutex , synchronized , 数据库锁 | 串行化访问,冲突低性能好 |
乐观锁 | “别人可能不会改,我试试看” | CAS , 版本号 , timestamp | 高并发高性能,失败需重试 |
🔒 一、悲观锁(Pessimistic Lock)
💡 原理:
悲观地认为:每次访问共享资源都会发生冲突,所以先加锁再访问,保证线程安全。
🧰 实现手段:
- C++ 中用
std::mutex
,std::lock_guard
- 操作系统里用 互斥量、读写锁
- 数据库中用:
SELECT FOR UPDATE
(阻塞其他事务)
示例(C++):
std::mutex mtx;
void critical_section() {
std::lock_guard<std::mutex> lock(mtx); // 自动加锁解锁
shared_data++;
}
✅ 特点:
- 简单稳定 ✅
- 没有冲突也要加锁 ❌(导致性能损耗)
- 在低并发、高冲突场景中性能较好
😄 二、乐观锁(Optimistic Lock)
💡 原理:
乐观地认为:别人不会同时修改资源,可以先访问,再校验有没有冲突。
如果发现冲突,再重试一次。
🧰 实现方式:
- 使用 版本号、时间戳 或原子操作(如 CAS: Compare And Swap)
示例(CAS 写法):
#include <atomic>
std::atomic<int> shared_val = 0;
void try_update() {
int expected = shared_val;
int new_val = expected + 1;
// 如果当前值还是 expected,才写入 new_val
while (!shared_val.compare_exchange_weak(expected, new_val)) {
expected = shared_val; // 更新预期值,准备下一次 retry
new_val = expected + 1;
}
}
✅ 乐观锁特点:
- 冲突少时性能非常好(几乎无锁)🚀
- 需要额外机制保证一致性(如版本号、CAS)🔁
- 并发越高,失败重试越频繁(开销反而大)⚠️
🎯 举个现实比喻:
场景 | 悲观锁 | 乐观锁 |
---|---|---|
厕所上锁 | 锁门才能进 → 一次只进一个人 | 推门进,没人就进,有人就退出重试 |
打开文档编辑 | 必须申请编辑权限,别人就不能动 | 大家都可以打开,保存时如果冲突就提示你冲突了 |
🧠 总结对比表:
对比项 | 悲观锁 | 乐观锁 |
---|---|---|
思维模式 | 默认会冲突 | 默认不会冲突 |
是否加锁 | 是(阻塞) | 否(非阻塞) |
实现方式 | mutex , rwlock 等 | CAS , 版本号,时间戳 |
性能特点 | 冲突多时稳定 | 冲突少时快,冲突多性能下降 |
成功概率 | 一般都能成功 | 可能失败需重试 |
使用场景 | 高冲突区,如数据库写操作 | 高并发低冲突,如计数器、自旋队列 |
🚀 应用场景举例
场景 | 适合哪种锁 | 原因 |
---|---|---|
数据库事务并发写 | 悲观锁 | 写冲突概率高,稳定性更重要 |
高性能并发计数器 | 乐观锁 | 并发高,原子操作更轻量 |
多线程日志输出 | 悲观锁 | 串行化写入,避免数据混乱 |
并发哈希表(无锁容器) | 乐观锁 | CAS 操作 + 重试,无锁更快 |