读写锁
读写锁原理
读写锁是解决 读多写少 场景的并发控制机制
并发环境下,当多个线程同时读取共享数据时,使用读写锁可以允许多个线程过去读锁,提高读锁效率;
当某线程需要更新共享数据时,需要获取写锁,此时所有读线程和写线程都被阻塞,保证数据的一致性;
读写锁实现
Java 通过 ReentrantReadWriteLock 类实现读写锁。
当没有线程持有写锁时,多个线程可以同时持有读锁,从而允许并发读取操作;
当一个线程持有写锁时,其他线程无法持有读锁和写锁,从而保证写操作的互斥性;
三大特性:
- 公平性:支持公平和非公平两种模式。
- 重入性:支持重入,读写锁都支持最多65535个。
- 锁降级:先获取写锁,再获取读锁,再释放写锁,写锁就能降级为读锁。
读写锁的过程
实现过程:
- 获取读锁(ReadLock):
- 通过调用 readLock() 方法获取读锁示例
- 当线程持有读锁时,其他线程可以同时持有读锁,允许并发读取操作
- 如果其他线程持有写锁,则读取读锁线程会被阻塞,知道写锁被释放(保证写锁操作的原子性和一致性)
- 获取写锁(WriteLock):
- 通过 writeLock() 方法获取读写锁示例
- 写锁是占有锁,同一时刻只有一个线程持有写锁
- 如果其他线程持有读锁或写锁时,则获取写锁的线程会被阻塞,直到所有读锁和写锁被释放
- 释放锁:
- 无论读锁还是写锁,在完成操作后都应该调用 unlock() 方法释放锁,以便其他线程可以获取锁
- 重入性:
- ReentrantReadWriteLock 支持锁的重入性,即同一个线程可以多次获取同一种类型的锁而不会造成死锁
- 常见场景:token缓存没有刷新机制,只能在读取使用过程,发现过期后刷新并重新读取,此时还是同线程
代码示例:
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReentrantReadWriteLockExample {
private static ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private static ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
private static ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
private static int sharedData = 0;
public static void main(String[] args) {
// 获取读锁并进行读操作
readLock.lock();
try {
// 读取共享数据
System.out.println("Reading data: " + sharedData);
} finally {
readLock.unlock();
}
// 获取写锁并进行写操作
writeLock.lock();
try {
// 修改共享数据
sharedData = 42;
System.out.println("Writing data: " + sharedData);
} finally {
writeLock.unlock();
}
}
}
特殊场景(锁降级)
锁降级指的是一个持有写锁降级为读锁的过程。帮助减少锁的持有时间,提高并发性能,并且避免潜在的死锁情况
实现过程:
- 获取写锁
- 对共享资源进行读写操作
- 线程先获取读锁,再释放写锁
- 释放写锁(此时线程依然持有读锁)
锁降级是依赖读锁的共享特性,多个线程可以同时持有读锁;锁升级是不被允许的,因为读锁共享,写锁却仅能存在一个
代码示例:
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class LockDowngradingExample {
private static ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private static ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
private static ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
private static int sharedData = 0;
public static void main(String[] args) {
// 获取写锁
writeLock.lock();
try {
// 操作共享资源
sharedData = 42;
System.out.println("Writing data: " + sharedData);
// 锁降级
readLock.lock();
} finally {
// 释放写锁
writeLock.unlock();
}
try {
// 继续读取共享资源
System.out.println("Reading data: " + sharedData);
} finally {
// 释放读锁
readLock.unlock();
}
}
}
源码解析:ReentrantReadWriteLock读写锁解析-腾讯云开发者社区-腾讯云 (tencent.com)
遇到的问题:当一个线程获取写锁时,仅是会将其余线程动作拦在 “门外”,但写锁操作完成释放后,其余线程岂不是还是会继续写锁吗?
死锁
待拓展…
当知识遇见实践,智慧得以升华
就像此次实现 token 缓存机制,恰好可以借助 Java 锁机制来提升系统性能和数据安全性