常见锁策略
在多线程编程和分布式系统中,锁策略是保证数据一致性和系统稳定性的核心机制。不同的场景需要匹配不同的锁策略,本文将系统性地解析常见的锁策略及其适用场景(只解释思想,不涉及实现)。
一、乐观锁 vs 悲观锁
乐观锁(Optimistic Locking)
假设数据一般情况下不会产生并发冲突,所以在数据进行提交更新的时候,才会正式对数据是否产生并发冲突进行检测,如果发现并发冲突了,则让返回用户错误的信息,让用户决定如何去做。
原理:假设并发冲突概率低,先操作数据,提交时检测版本(如版本号、时间戳)。
场景:读多写少(电商库存更新、文档协作编辑)
优点:减少锁开销,提升吞吐量
缺点:高冲突时频繁重试
悲观锁(Pessimistic Locking)
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。
原理:默认会发生冲突,操作前先加锁(如synchronized
、SELECT FOR UPDATE
)
场景:写操作频繁(银行转账、票务系统)
优点:保证强一致性
缺点:增加线程阻塞风险
二、读写锁(ReadWrite Lock)
多线程之间,数据的读取方之间不会产生线程安全问题,但数据的写入方互相之间以及和读者之间都需要进行互斥。如果两种场景下都用同一个锁,就会产生极大的性能损耗。所以读写锁因此而产生。 读写锁(readers-writer lock),看英文可以顾名思义,在执行加锁操作时需要额外表明读写意图,复数读者之间并不互斥,而写者则要求与任何人互斥。
一个线程对于数据的访问, 主要存在两种操作: 读数据 和 写数据.
- 两个线程都只是读一个数据, 此时并没有线程安全问题. 直接并发的读取即可.
- 两个线程都要写一个数据, 有线程安全问题.
- 一个线程读另外一个线程写, 也有线程安全问题.
核心机制
- 读锁共享:多个线程可并发读
- 写锁独占:写操作时禁止其他读写
典型场景
- 缓存系统(Guava Cache)
- 配置中心热更新
三、可重入锁(Reentrant Lock)
可重入锁的字面意思是“可以重新进入的锁”,即允许同一个线程多次获取同一把锁。
比如一个递归函数里有加锁操作,递归过程中这个锁会阻塞自己吗?如果不会,那么这个锁就是可重入 锁(因为这个原因可重入锁也叫做递归锁)。
Java里只要以Reentrant开头命名的锁都是可重入锁,而且JDK提供的所有现成的Lock实现类,包括 synchronized关键字锁都是可重入的。
关键特性
- 线程可重复获取同一把锁
- 避免死锁(递归调用场景)
实现方式
- Java的
synchronized
关键字 ReentrantLock
类
四、公平锁 vs 非公平锁
公平锁: 遵守 "先来后到". B 比 C 先来的. 当 A 释放锁的之后, B 就能先于 C 获取到锁.
非公平锁: 不遵守 "先来后到". B 和 C 都有可能获取到锁.
特性 | 公平锁 | 非公平锁 |
---|---|---|
获取顺序 | 按请求顺序分配 | 允许插队 |
吞吐量 | 较低(维护队列开销) | 较高 |
饥饿风险 | 无 | 可能存在 |
五、自旋锁(Spin Lock)
工作原理
- 线程循环检测锁状态(while循环),而非立即阻塞
- 示例:AtomicBoolean实现
while (!lock.compareAndSet(false, true)) {// 自旋等待
}
如果获取锁失败, 立即再尝试获取锁, 无限循环, 直到获取到锁为止. 第一次获取锁失败, 第二次的尝试会 在极短的时间内到来.
一旦锁被其他线程释放, 就能第一时间获取到锁
适用场景
- 锁持有时间极短(纳秒级)
- 内核开发、短任务同步
六、重量级锁vs轻量级锁
重量级锁
- 定义:直接依赖操作系统互斥量(Mutex)实现的锁机制
- 典型实现:Java
synchronized
关键字(未优化前)、ReentrantLock
默认模式 - 核心特点:线程竞争时会触发上下文切换,导致线程阻塞
轻量级锁
- 定义:基于CAS(Compare-And-Swap)等原子操作实现的乐观锁
- 典型实现:Java偏向锁、自旋锁、
AtomicInteger
等原子类 - 核心特点:通过忙等待(Busy-Waiting) 避免线程阻塞
核心差异对比
维度 | 重量级锁 | 轻量级锁 |
---|---|---|
实现层级 | 操作系统内核态 | 用户空间实现 |
线程阻塞 | 立即挂起(上下文切换) | 自旋等待(CPU空转) |
响应延迟 | 微秒级 | 纳秒级 |
内存消耗 | 每个锁关联内核对象(~1KB) | 仅需存储标记位(~4B) |
适用场景 | 高竞争、长临界区 | 低竞争、短临界区 |
典型CPU消耗 | 低(线程休眠) | 高(持续自旋) |
锁策略选择指南
- 冲突概率:高冲突选悲观锁,低冲突选乐观锁
- 读写比例:读多写少用读写锁
- 响应要求:低延迟场景慎用公平锁
- 系统架构:分布式环境需CAP权衡
"没有最好的锁,只有最合适的锁。" —— 分布式系统设计原则