9_读写锁

Posted root_zhb

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了9_读写锁相关的知识,希望对你有一定的参考价值。

1、概念

JAVA 的并发包提供了读写锁 ReentrantReadWriteLock
它表示两个锁,一个是读操作相关的锁,称为共享锁;一个是写相关的锁,称为排他锁

  1. 读锁:针对同一份数据,多个读操作可以同时进行而不会互相影响。
    写锁:当前操作没有完成之前,它会阻断其他写锁和读锁。
  2. 线程进入读锁的前提条件:
    • 没有其他线程的写锁
    • 没有写请求, 或者有写请求,但调用线程和持有锁的线程是同一个(可重入锁)。
  3. 线程进入写锁的前提条件:
    • 没有其他线程的读锁
    • 没有其他线程的写锁
  4. 读写锁三个重要特征
    • 公平选择性:支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平。
    • 重进入:读锁和写锁都支持线程重进入。
    • 锁降级:遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读

2、代码

代码讲解:对于 MyMap1 类中的 read 和 write 操作,分别表示读写操作。运行代码可以看到:对于每个写操作,不会出现插队的情况,对于读操作会出现插队的情况。如果去掉所加的读锁和写锁,均会插队。

public class ReadWriteLockDemo {

    public static void main(String[] args) throws InterruptedException {
        //创建减法计数器对象
        CountDownLatch latch = new CountDownLatch(6);
        MyMap1 myMap = new MyMap1();

        for (int i = 1; i <= 6; i++) {
            final int temp = i;
            new Thread(()->{
                myMap.write(temp, UUID.randomUUID().toString().substring(0,5));
                //减法计数器减 1
                latch.countDown();
            },"线程" + i).start();
        }
        // 此行代码以后的程序进入阻塞状态,直到减法计数器减到 0 为止,然后开始执行下面的代码
        latch.await();

        for (int i = 1; i <= 6; i++) {
            final int temp = i;
            new Thread(()->{
                myMap.read(temp);
            },"线程" + (i + 6)).start();
        }
    }
}

class MyMap1{

    volatile Map<Integer,String> map = new HashMap<>();
    //创建读写锁对象
    ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    public Map<Integer, String> getMap() {
        return map;
    }

    void read(Integer key){
        //给读锁加锁
        readWriteLock.readLock().lock();

        try {
            System.out.println(Thread.currentThread().getName() + "读取的key为:" + key);
            String result = map.get(key);
            System.out.println(Thread.currentThread().getName() + "读取key的结果为:" +  result);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //给读锁解锁
            readWriteLock.readLock().unlock();
        }
    }

    void write(Integer key,String value){
        //给写锁加锁
        readWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "执行写操作,key为:" + key + ",value为:" + value);
            map.put(key,value);
            System.out.println(Thread.currentThread().getName() + "执行写操作完毕");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //给写锁解锁
            readWriteLock.writeLock().unlock();
        }
    }
}

3、锁降级

对于获得写锁的线程,它一定独占了读写锁,因此可以继续让它获取读锁,当它同时获取了写锁和读锁后,还可以先释放写锁继续持有读锁,这样一个写锁就“降级”为了读锁。

锁降级中读锁的获取是否必要呢?
答案是必要的。主要是为了保证数据的可见性,如果当前线程不获取读锁而是直接释放写锁,假设此刻另一个线程(记作线程T)获取了写锁并修改了数据,那么当前线程无法感知线程T的数据更新。如果当前线程获取读锁,即遵循锁降级的步骤,则线程T将会被阻塞,直到当前线程使用数据并释放读锁之后,线程T才能获取写锁进行数据更新。

RentrantReadWriteLock不支持锁升级(把持读锁、获取写锁,最后释放读锁的过程)。
目的也是保证数据可见性,如果读锁已被多个线程获取,其中任意线程成功获取了写锁并更新了 数据,则其更新对其他获取到读锁的线程是不可见的。

以上是关于9_读写锁的主要内容,如果未能解决你的问题,请参考以下文章

java中ReentrantReadWriteLock读写锁的使用

互斥锁自旋锁读写锁和条件变量

Linux多线程_(线程池,读者写者,自旋锁)

第9章 线程编程_线程同步1:互斥锁

JUC高级多线程_07:读写锁与阻塞队列的具体介绍与使用

深入浅出 Java Concurrency (14): 锁机制 part 9 读写锁 (ReentrantReadWriteLock)