ReentrantReadWriteLock读写锁的使用
ReentrantReadWriteLock是juc下的一个并发读写锁。它可以进行精确的读写锁控制,通常用于读多写少的场景。
特点
通过readLock()方法和writeLock()获取响应的读锁和写锁。读锁其他线程可以共同进入,不会对读锁进行互斥。写锁只有在读锁释放后才能请求上锁。写锁上锁后读锁还能继续上锁。适合更新数据时读操作一起停止的场景。
实例
ReentrantReadWriteLock写法
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @author AJ
* @date 2021 2021-01-10 11:21
*/
public class ReadWriteLock {
public static volatile Map<String,String> map=new HashMap();
public static void main(String[] args) {
// map.put("k1","value");
String value="";
ReentrantReadWriteLock reentrantReadWriteLock=new ReentrantReadWriteLock();
//获取读写锁
ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
//加读锁
readLock.lock();
if(map.get("k1") == null){
System.out.println("k1 为空");
readLock.unlock(); //不释放读锁,会导致写锁无法上锁
writeLock.lock();
try{
if(map.get("k1") == null){ //dcl,双重检查,防止上锁途中其他线程已经插入数据
map.put("k1","value");
}
readLock.lock(); //锁降级,防止数据被其他线程篡改,导致脏读
}finally {
writeLock.unlock();
}
}
try{
System.out.println(map.get("k1")); //锁降级代码中获取数据,由于读锁已经获取到,其他线程无法获取到写锁了。保证数据为该线程设置的数据
}finally {
readLock.unlock();
}
}
}
该例子展示了一个简单的读写互斥的场景。在正常情况下map有值时,多线程读取不会受到影响。只有当map为空时,线程上写锁,此时所有的读锁和写锁都"暂停",等待写锁释放(包括读操作也会暂停!!!)。在写锁里面可以进行读锁上锁(锁降级),避免写锁释放读锁上锁的间隙时间被其他线程上写锁更新数据导致本线程读取到的数据和更新的数据不一致(脏数据)。
synchronized写法
public static void sync(){
System.out.println(map.get("k1")); //②
if(map.get("k1") == null){
synchronized (ReadWriteLock.class){
if(map.get("k1") == null){ //dcl判断
map.put("k1","value");
}
}
}
System.out.println(map.get("k1")); //②
}
synchronized的写法更为简单粗暴,可以直接对所有的读写操作都加锁后再进行查询,但是锁的粒度比较大,读操作也无法进行并发。或者像操作②一样,将读操作放在锁外面,但此时读操作无法控制,程序进行写操作的时候其他程序依旧可以进行读操作。
总结
需要在进行写入时控制读操作时可以使用ReentrantReadWriteLock,达到更精细的控制粒度,这个是synchronized无法做到的。实际应用场景方面,ReentrantReadWriteLock可以用在双重单例模式中的第一个判空。