多线程笔记(十三):可重入读写锁 ReentrantReadWriteLock

建议:学习本文前,建议先了解一下 可重入锁 ReentrantLock ,理解一下"可重入"是个什么概念  ^_^


概念

       既然已经有了 可重入锁 ReentrantLock,为什么还要有 可重入读写锁 ReentrantReadWriteLock 呢??

       因为 可重入锁 ReentrantLock 和 synchronized 一样,属于独占锁。所谓"独占",即:同一时间只能有一个线程持有锁。

       ReentrantReadWriteLock 的出现, 目的就是解决 ReentrantLock 独占锁 带来的性能问题。使用 ReentrantLock 无论是 "写/写"线程、"读/读"线程、"读/写"线程之间的工作都是互斥,同时只能有一个线程进入同步区域。

       然而大多数场景下,"读/读"线程之间并不会存在互斥的关系,只有"读/写"线程 或 "写/写"线程间的操作才需要互斥。因此,可重入读写锁 ReentrantReadWriteLock 就出现了。

特性  

       ReentrantReadWriteLock 实现的是 ReadWriteLock 接口。而并不是 Lock接口(ReentrantLock实现的是Lock接口)

       该接口提供了 readLock 和 writeLock 两种锁的操作机制,一个是读锁,一个是写锁。

       读锁是 共享锁,写锁是 独占锁(排他锁)

        实例:一个资源可以被多个读操作访问,或者一个资源一个写操作访问;但是一个资源不能同时 读操作 && 写操作 并行,这样有可能会导致脏数据的问题。

       ReentrantReadWriteLock 的出现,就是为了提高读操作的吞吐量。

可重入读写锁Demo

/**
 * TODO 可重入读写锁 ReentrantReadWriteLock
 *
 * @author liuzebiao
 * @Date 2019-12-25 18:33
 */
public class RWLockDemo {

    static Map<String, Object> cacheMap = new HashMap<>();

    static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    static Lock read = readWriteLock.readLock(); //读锁

    static Lock write = readWriteLock.writeLock();//写锁

    //场景:缓存的更新和读取时,
    //遇到问题:当前某个线程更新了数据。其他线程在更新前读到了这个数据,导致脏数据问题。
    //解决方法:可以通过读写锁的方式,来解决这个问题

    public static void get(String key) {
        //读锁(执行读操作时,需要获取一个读锁,并发访问时,并不会被阻塞。当我们有10个、100个线程访问时,加了读锁,并不会影响这100个线程的读操作)
        //因为读并不会改变数据的状态。可以认为没加锁一样,在此处只是一个锁的控制,读锁并不会被阻塞
        read.lock();
        try {
            System.out.println("读到:[key=" + key + ",value=" + cacheMap.get(key) + "]");
        } finally {
            read.unlock();
        }
    }

    public static void set(String key, Object value) {
        //写锁(执行写操作时,线程必须获得一个写锁,此时,当已经有其他线程获得了写锁的话,当前线程在写的时候,会因获取不到这个锁而被阻塞)
        //只有当这个锁被释放以后,其他的写操作才能继续
        write.lock();
        try {
            cacheMap.put(key,value);
            System.out.println("写入:[key=" + key + ",value="+value + "]");
        } finally {
            write.unlock();
        }
    }

    //某线程获得写锁后,其他线程去读 get()读操作时,会被阻塞在 read.lock() 处。即当前获得写锁,以为这当前数据有可能发生变化。
    //其他线程在get()获得数据时,有可能造成脏数据。所以在 read.lock() 处会阻塞。当写锁被释放后, read.lock() 处的读操作就能继续进行

    public static void main(String[] args) {
        //写线程(开启3个线程)
        //因为读线程并不会被阻塞,所以 i 定义从25开始,读写线程有交集,从而能够保证读线程能够读取到写入的值
        for (int i = 25; i < 28; i++) {
            String j = String.valueOf(i);
            new Thread(() -> {
                RWLockDemo.set("read" + j, Math.ceil(Math.random()*1000));
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
        //读线程(开启30个线程)
        for (int i = 0; i < 30; i++) {
            String j = String.valueOf(i);
            new Thread(() -> {
                RWLockDemo.get("read"+j);
            }).start();
        }
    }
}

测试结果

       你拿 demo 自己测试一下,你就会发现读操作根本不会有任何阻塞,是瞬间进行的。而写操作就不一样了。

       本实例基本 3秒 完事,如果你将本例 ReentrantReadWriteLock 换做 ReentrantLock(独占锁),那时间就得基本 33秒 才能完成了。

读到:[key=read0,value=null]
读到:[key=read1,value=null]
读到:[key=read3,value=null]
写入:[key=read25,value=651.0]
写入:[key=read27,value=552.0]
写入:[key=read26,value=870.0]
读到:[key=read2,value=null]
读到:[key=read6,value=null]
读到:[key=read11,value=null]
读到:[key=read4,value=null]
读到:[key=read8,value=null]
读到:[key=read13,value=null]
读到:[key=read15,value=null]
读到:[key=read7,value=null]
读到:[key=read9,value=null]
读到:[key=read12,value=null]
读到:[key=read24,value=null]
读到:[key=read25,value=651.0]
读到:[key=read26,value=870.0]
读到:[key=read28,value=null]
读到:[key=read23,value=null]
读到:[key=read27,value=552.0]
读到:[key=read21,value=null]
读到:[key=read18,value=null]
读到:[key=read17,value=null]
读到:[key=read16,value=null]
读到:[key=read14,value=null]
读到:[key=read5,value=null]
读到:[key=read22,value=null]
读到:[key=read20,value=null]
读到:[key=read19,value=null]
读到:[key=read10,value=null]
读到:[key=read29,value=null]

原理分析

    请参考大神文章:https://www.jianshu.com/p/9f98299a17a5


可重入读写锁 ReentrantReadWriteLock 的用法,介绍到此为止

如果本文对你有所帮助,那就给我点个赞呗 ^_^ 

End

发布了247 篇原创文章 · 获赞 44 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/lzb348110175/article/details/103703626