共享锁(Shared Lock)是一种并发控制机制,允许多个线程同时读取共享资源,但在写操作时会阻塞其他线程的读写请求。它通过“读共享、写独占”的策略,在保证数据一致性的同时提升了读操作的并发性能。
核心原理
读写分离策略
- 读锁(共享锁):允许多个线程同时获取,用于读取操作。
- 写锁(排他锁):同一时间仅允许一个线程获取,用于写入操作。
锁兼容性
- 读锁之间:相互兼容,可同时持有。
- 读锁与写锁:互斥,写锁需等待所有读锁释放。
- 写锁之间:互斥,同一时间仅一个写锁。
共享锁的优缺点
优点
- 读并发提升:适合读多写少场景,如缓存系统、数据库查询。
- 数据一致性:写操作时独占资源,避免脏写。
缺点
- 写饥饿风险:若读请求频繁,写操作可能长时间等待。
- 实现复杂度高:需维护锁状态和等待队列。
实现方式
1. 数据库层面
- MySQL:
SELECT * FROM table WHERE id = ? LOCK IN SHARE MODE; -- 共享读锁
- 其他事务可读取该行,但无法修改,直至当前事务提交。
2. Java语言实现
ReentrantReadWriteLock:
import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; public class ReadWriteLockExample { private final ReadWriteLock rwLock = new ReentrantReadWriteLock(); private int data = 0; public int readData() { rwLock.readLock().lock(); // 获取读锁 try { return data; } finally { rwLock.readLock().unlock(); // 释放读锁 } } public void writeData(int newValue) { rwLock.writeLock().lock(); // 获取写锁 try { data = newValue; } finally { rwLock.writeLock().unlock(); // 释放写锁 } } }
共享锁的典型应用场景
缓存系统
- 读操作获取共享锁,写操作(如缓存更新)获取排他锁。
数据库查询
- 多事务并发读取同一数据时,使用共享锁避免阻塞。
配置管理
- 频繁读取、偶尔修改的配置文件。
与其他锁的对比
锁类型 | 读并发 | 写并发 | 互斥对象 | 适用场景 |
---|---|---|---|---|
共享锁 | 允许多个 | 独占 | 写锁 | 读多写少,如数据库查询 |
排他锁 | 独占 | 独占 | 读锁、写锁 | 写操作为主,如文件写入 |
乐观锁 | 无锁 | CAS检查 | 无(冲突重试) | 冲突少的读多写少场景 |
悲观锁 | 独占 | 独占 | 所有操作 | 冲突多的写操作场景 |
注意事项
写饥饿问题
- 高并发读场景下,写锁可能长时间等待。
- 解决方案:使用公平锁(如
ReentrantReadWriteLock(true)
)。
锁降级
- 写锁可降级为读锁,但读锁不能升级为写锁(可能导致死锁)。
public void upgradeLockExample() { rwLock.writeLock().lock(); try { // 写操作 rwLock.readLock().lock(); // 降级:先获取读锁 rwLock.writeLock().unlock(); // 再释放写锁 // 继续读操作 } finally { rwLock.readLock().unlock(); } }
性能权衡
- 锁状态管理开销较大,需根据业务特点选择是否使用。
Java中的共享锁工具
ReentrantReadWriteLock
- 可重入的读写锁实现,支持公平/非公平模式。
StampedLock(JDK 8+)
- 支持乐观读锁,在读多写少场景下性能更优。
import java.util.concurrent.locks.StampedLock; public class StampedLockExample { private final StampedLock lock = new StampedLock(); private int data = 0; public int optimisticRead() { long stamp = lock.tryOptimisticRead(); // 获取乐观读戳记 int currentData = data; if (!lock.validate(stamp)) { // 检查期间是否有写操作 stamp = lock.readLock(); // 升级为悲观读锁 try { currentData = data; } finally { lock.unlockRead(stamp); } } return currentData; } }
总结
共享锁通过“读共享、写独占”的策略,在保证数据一致性的前提下提升了读操作的并发性能,尤其适合读多写少的场景。但需注意写饥饿问题和锁升级/降级的正确使用。在高并发系统中,合理使用共享锁可显著优化吞吐量,但需根据业务特点进行性能测试和调优。