在一些需要对一个资源进行频繁的读写操作且读多写少的情况,那么通常使用的是读写锁。
认识ReadwriterLock
ReadWriteLock并不是Lock锁接口的扩展类,只是在底层实现上借助Lock接口对资源的访问控制。
我们可以看到这个接口下区分了读锁、写锁。那么它们是否区分开了读、写的对象呢?让我们看看子类实现。
ReentrantReadWriteLock
从源码上来看,读写锁都是对同一个资源进行加锁。接下来我们看看读写锁的特性有哪些,这样可以帮助我们在分析什么问题与需求时有所帮助
特性
- 公平性:非公平锁(默认),在线程获取的是读锁时不会发生锁竞争。所以读操作没有公平性和非公平性。写操作时,可能因为时间原因无法立刻获取到锁,所以期间可能会发生一个或者多个读操作和写操作,因此非公平的锁的吞吐量会优于公平锁(因为会发生锁竞争以及出入队)。
- 重入性:读写锁允许读线程和写线程按照请求锁的顺序来重新获取读取锁或者写入锁。但写锁释放后才能读锁才能进行重入。写线程获取写锁后重新获取也可以进行重入锁,读线程获取读锁后不能获取写锁。
- 锁降级:线程获取写锁后在进行释放,写锁降级为读锁。
- 锁升级:读取锁是不能直接升级为写入锁的,因为一个写入锁需要释放读取锁,所以如果两个读取锁获取写入锁,且都不释放那么会造成死锁。
- 锁获取中断:在写锁和读锁在获取锁期间都可以被打断。
- 条件变量:写入锁提供条件变量(condition)支持,但是读锁却不允许获取条件变量。
- 重入数:读锁与写锁的重入数最大为65535
总结来看的:
- 读-读不互斥:因为两个读取锁的线程互不干扰,可以并发读。
- 写-写互斥:因为在同一个时刻只能有一个写线程获取到写锁
- 读-写互斥:因在线程获取了读锁后在去获取写锁,读锁是会被释放的。
使用ReentReadWriterLock
import java.util.HashMap;
import java.util.Random;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.IntStream;
public class ReentrantReadWriteLockTest {
//创建非公平方式
private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
private final HashMap<String, String> CACHE_MAP = new HashMap<>();
public void put(String key) {
reentrantReadWriteLock.writeLock().lock();
try {
if (!CACHE_MAP.containsKey(key)) {
//模拟从数据库获取数据并保存到缓存中
String DEFAULT_VALUE = "111";
CACHE_MAP.put(key, DEFAULT_VALUE);
}
} finally {
reentrantReadWriteLock.writeLock().unlock();
}
}
public String getByKey(String key) {
//当我们 尝试从map里面获取一个数据 如果不存在就先
reentrantReadWriteLock.readLock().lock();
String message = CACHE_MAP.get(key);
try {
if (null == message) {
reentrantReadWriteLock.readLock().unlock();
//调用put方法 插入元素
put(key);
reentrantReadWriteLock.readLock().lock();
message = CACHE_MAP.get(key);
}
} finally {
reentrantReadWriteLock.readLock().unlock();
}
return message;
}
public static void main(String[] args) {
ReentrantReadWriteLockTest readWriteLockTest = new ReentrantReadWriteLockTest();
Random random = new Random();
int i = random.nextInt(1000);
IntStream.range(1,10).mapToObj(v->new Thread(()->{
String value= readWriteLockTest.getByKey(random.nextInt(10) + "");
System.out.println(Thread.currentThread().getName()+"----"+value);
})).forEach(Thread::start);
}
}
比较ReentrantLock与ReentrantReadWriteLock
- 相同点:两者都是显示锁,需要手动加锁解锁。也适合高并发场景。
- 不同点:ReentrantReadWriteLock是ReentrantLock的复杂实现。从底层的设计上来看,适用于资源读写频繁的场景。