悲观锁(Pessimistic Locking)
🔒 核心原理
先加锁后操作,假设每次数据访问都会冲突。通过独占资源确保安全。
⚙️ 实现方式
-
数据库实现:
BEGIN; SELECT * FROM accounts WHERE id=1 FOR UPDATE; -- 加行锁 UPDATE accounts SET balance=balance-100 WHERE id=1; COMMIT;
-
Java 实现:
// synchronized 实现 public synchronized void transfer(Account from, Account to, int amount) { from.debit(amount); to.credit(amount); } // ReentrantLock 实现 private final Lock lock = new ReentrantLock(); public void transfer() { lock.lock(); try { // 业务逻辑 } finally { lock.unlock(); } }
🎯 适用场景
-
银行转账(高冲突)
-
库存扣减(强一致性要求)
-
订单状态变更(需严格顺序)
⚠️ 风险与规避
风险 | 解决方案 |
---|---|
死锁 | 统一资源获取顺序 + 锁超时机制 |
性能瓶颈 | 缩小锁粒度(行锁代替表锁) |
长事务阻塞 | 设置事务超时 innodb_lock_wait_timeout=5 |
乐观锁(Optimistic Locking)
🌟 核心原理
先操作后校验,假设冲突概率低。通过版本号/时间戳检测数据变化。
⚙️ 实现方式
-
数据库版本号:
UPDATE products SET stock = stock - 1, version = version + 1 WHERE id = 100 AND version = 5; -- 版本校验
-
CAS 原子操作:
AtomicInteger balance = new AtomicInteger(1000); balance.compareAndSet(expect, update); // Java 原子类
🎯 适用场景
-
电商库存(读多写少)
-
评论点赞计数
-
用户信息更新(低冲突场景)
💡 优化策略
-
指数退避重试:首次重试间隔50ms,后续加倍
-
批量合并提交:攒多次操作一次性校验
-
分离热点数据:如库存拆分为100个分桶
读写锁(Read-Write Lock)
🔄 锁兼容矩阵
读锁 | 写锁 | |
---|---|---|
读锁 | ✅ | ❌ |
写锁 | ❌ | ❌ |
⚙️ Java 实现
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
void readData() {
rwLock.readLock().lock();
try {
// 并发读操作
} finally {
rwLock.readLock().unlock();
}
}
void writeData() {
rwLock.writeLock().lock();
try {
// 独占写操作
} finally {
rwLock.writeLock().unlock();
}
}
🚀 性能对比(100线程测试)
操作比例 | 互斥锁QPS | 读写锁QPS | 提升 |
---|---|---|---|
读90%写10% | 1,200 | 10,800 | 9x |
读70%写30% | 1,500 | 4,500 | 3x |
🎯 适用场景
-
配置中心(配置读取占99%)
-
商品详情页缓存
-
黑白名单查询系统
⚠️ 注意事项
-
写锁饥饿:连续读请求可能阻塞写请求
-
锁降级:允许写锁降级为读锁(反之不可)
rwLock.writeLock().lock(); try { // 写操作 rwLock.readLock().lock(); // 降级开始 } finally { rwLock.writeLock().unlock(); // 降级完成 }
自旋锁(Spin Lock)
⚡ 核心原理
忙等待替代线程挂起,避免上下文切换开销。
⚙️ 实现机制
public class SpinLock {
private AtomicBoolean locked = new AtomicBoolean(false);
public void lock() {
while (!locked.compareAndSet(false, true)) {
// 自旋等待(JDK9+用Thread.onSpinWait()优化)
}
}
public void unlock() {
locked.set(false);
}
}
⏱️ 性能临界点
CPU架构 | 自旋有效阈值 |
---|---|
x86 | < 100 纳秒 |
ARM | < 50 纳秒 |
🎯 适用场景
-
内核中断处理
-
Disruptor无锁队列
-
Java的AQS同步器底层
⚠️ 风险控制
-
自适应自旋:JDK6+ 根据历史等待时间动态调整自旋次数
-
超时熔断:设置最大自旋次数(如1000次)后挂起线程
分布式锁(Distributed Lock)
🌐 实现方案对比
方案 | 实现原理 | CP/AP | 性能 | 推荐场景 |
---|---|---|---|---|
Redis | SETNX + Lua脚本 | AP | 高 | 秒杀、缓存击穿 |
ZooKeeper | 临时有序节点 + Watch | CP | 中 | 配置管理、选主 |
etcd | Lease + Revision比较 | CP | 高 | 服务注册发现 |
DB | 唯一约束/乐观锁 | CP | 低 | 不推荐生产 |
⚙️ Redis 红锁(RedLock)实现
// Redisson 实现
RLock lock = redisson.getLock("orderLock");
try {
if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {
// 业务操作
}
} finally {
lock.unlock();
}
🔒 关键要求
-
互斥性:任一时刻仅一个客户端持有锁
-
容错性:客户端崩溃后锁自动释放
-
防死锁:必须有超时机制
⚠️ 常见陷阱
问题 | 解决方案 |
---|---|
时钟漂移 | 使用TTL代替时间戳 |
GC暂停导致超时 | 追加随机token校验持有者 |
脑裂问题 | 要求多数节点(N/2+1)确认 |
意向锁(Intention Lock)
🧩 层级锁设计
⚙️ 加锁规则
-
加行级S锁前 → 先加IS锁
-
加行级X锁前 → 先加IX锁
-
加表锁时检查意向锁冲突
💡 核心价值
-
快速冲突检测:避免逐行检查锁状态
-
减少死锁:表级锁与行级锁解耦
间隙锁(Gap Lock)与临键锁(Next-Key Lock)
🔍 锁范围图示
数据:10, 20, 30
间隙:(-∞,10), (10,20), (20,30), (30,+∞)
临键锁:(10,20] = 20的记录锁 + (10,20)的间隙锁
⚙️ 触发场景
间隙锁(阻止15插入)
SELECT * FROM users WHERE age BETWEEN 10 AND 20 FOR UPDATE;
临键锁(InnoDB默认)
SELECT * FROM users WHERE age > 15 FOR UPDATE; -- 锁定(15, +∞)
🛡️ 幻读防护机制
隔离级别 | 防幻读机制 |
---|---|
RC | 无防护 |
RR | 临键锁 |
Serializable | 表级锁 |
⚠️ 注意事项
-
仅RR级别生效
-
无索引退化为表锁
-
范围查询性能影响
锁机制对比总表
锁类型 | 冲突策略 | 实现复杂度 | 吞吐量 | 典型场景 |
---|---|---|---|---|
悲观锁 | 预加锁 | ★★☆ | 中 | 账户转账 |
乐观锁 | 后校验 | ★☆☆ | 高 | 商品库存 |
读写锁 | 读共享 | ★★★ | 极高 | 配置中心 |
自旋锁 | 忙等待 | ★★☆ | 超高 | 无锁队列 |
分布式锁 | 跨节点 | ★★★★ | 低 | 集群任务调度 |
意向锁 | 层级控制 | ★★★ | 高 | 数据库表锁管理 |
间隙锁 | 范围控制 | ★★★★ | 中 | 防止幻读 |
最佳实践指南
-
锁选择决策树:
通用优化原则:
-
锁粒度:行锁 > 表锁,分段锁 > 全局锁
-
持锁时间:< 10ms(超过则异步化)
-
监控指标:锁等待时间、死锁次数、QPS下降率
故障排查命令:
MySQL 锁分析
SHOW ENGINE INNODB STATUS; -- 查看锁信息
SELECT * FROM sys.innodb_lock_waits; -- 锁等待关系
Java 线程分析
jstack <pid> > thread_dump.txt -- 查看锁竞争