读写锁(ReentranReadWriteLock))详解

基本介绍

  • ReadWriteLock同Lock一样也是一个接口,提供了readLock和writeLock两种锁的操作机制,一个是只读的锁,一个是写锁。ReentranReadWriteLock是其实现类

  • 读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的(排他的)。 每次只能有一个写线程,但是可以有多个线程并发地读数据。

  • 所有读写锁的实现必须确保写操作对读操作的内存影响。换句话说,一个获得了读锁的线程必须能看到前一个释放的写锁所更新的内容。

  • 理论上,读写锁比互斥锁允许对于共享数据更大程度的并发。与互斥锁相比,读写锁是否能够提高性能取决于读写数据的频率、读取和写入操作的持续时间、以及读线程和写线程之间的竞争。

读写锁的使用场景

  • 在一些共享资源的读和写操作,且写操作没有读操作那么频繁的场景下可以用读写锁

在没有写操作的时候,两个线程同时读一个资源没有任何问题,所以应该允许多个线程能在同时读取共享资源。但是如果有一个线程想去写这些共享资源,就不应该再有其它线程对该资源进行读或写。这就需要一个读/写锁来解决这个问题

互斥原则:

  • 读-读能共存(即可以用多个线程同时的读)
  • 读-写不能共存(即读的时候不能有 其他线程去修改,或者修改的时候不能有其他线程去读)
  • 写-写不能共存(即修改的时候不能再有其他线程去修改)

代码示例

先看一个不加锁的读写操作

public class ReadWriteLockTest {
    // 可重入的读写锁
    ReadWriteLock readWriteLock = new ReentrantReadWriteLock();


    public static void main(String[] args) {
        MyData myData = new MyData();
        for (int i = 1; i <= 5; i++) {
            final int key = i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    myData.put(key + "", String.valueOf(key));
                }
            }, "t"+i).start();
        }

        for (int i = 6; i <= 10; i++) {
            final int key = i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    String s = myData.get((key - 5) + "");
                }
            }, "t"+i).start();
        }
    }
}

class MyData{
    private volatile Map<String ,String> map = new HashMap<>();

    public void put(String key,String value) {
        System.out.println(Thread.currentThread().getName() + " 正在写入");
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        map.put(key,value);
        System.out.println(Thread.currentThread().getName() + " 写入完成");
    }

    public String get(String key) {
        System.out.println(Thread.currentThread().getName() + " 正在读取");
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        String v = map.get(key);
        System.out.println(Thread.currentThread().getName() + " 读取完成,读到数据:" + v);
        return v;
    }
}

运行结果:

t1 正在写入
t5 正在写入
t4 正在写入
t2 正在写入
t3 正在写入
t6 正在读取
t8 正在读取
t10 正在读取
t7 正在读取
t9 正在读取
t2 写入完成
t3 写入完成
t4 写入完成
t1 写入完成
t5 写入完成
t10 读取完成,读到数据:5
t8 读取完成,读到数据:null
t6 读取完成,读到数据:1
t7 读取完成,读到数据:null
t9 读取完成,读到数据:null

从结果可以看出,对共享资源进行读写操作,如果不加控制,就出出现读写数据错误。

在看已下代码,加入读写锁控制:

public class ReadWriteLockTest {
    public static void main(String[] args) {
        MyData myData = new MyData();
        for (int i = 1; i <= 5; i++) {
            final int key = i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    myData.put(key + "", String.valueOf(key));
                }
            }, "t"+i).start();
        }

        for (int i = 6; i <= 10; i++) {
            final int key = i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    String s = myData.get((key - 5) + "");
                }
            }, "t"+i).start();
        }
    }
}

class MyData{
    private volatile Map<String ,String> map = new HashMap<>();
    // 可重入的读写锁
    ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    public void put(String key,String value) {
        try {
            readWriteLock.writeLock().lock();
            System.out.println(Thread.currentThread().getName() + " 正在写入");
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            map.put(key,value);
            System.out.println(Thread.currentThread().getName() + " 写入完成");
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }

    public String get(String key) {
        try {
            readWriteLock.readLock().lock();
            System.out.println(Thread.currentThread().getName() + " 正在读取");
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            String v = map.get(key);
            System.out.println(Thread.currentThread().getName() + " 读取完成,读到数据:" + v);
            return v;
        } finally {
            readWriteLock.readLock().unlock();
        }
    }
}

输出结果:

t1 正在写入
t1 写入完成
t2 正在写入
t2 写入完成
t3 正在写入
t3 写入完成
t4 正在写入
t4 写入完成
t5 正在写入
t5 写入完成
t6 正在读取
t7 正在读取
t8 正在读取
t9 正在读取
t10 正在读取
t6 读取完成,读到数据:1
t10 读取完成,读到数据:5
t7 读取完成,读到数据:2
t9 读取完成,读到数据:4
t8 读取完成,读到数据:3

对比没有加入读写锁之前的输出结果,我们看出,

  1. 写操作的时候,当一个线程在写的时候,别的线程无法插入的,只有当这个线程写完成的时候,别的线程才能 进行写操作,
  2. 读的时候时候别的线程可以进行共享读,但是在写的时候别的线程也是无法进行读的
  3. 通过以上可以得出,写写互斥,读写互斥,读读共享

另外ReentrantReadWriteLock也是可重入锁,构造方法可以传入boolean类型,表示是公平与非公平锁

你可能感兴趣的:(JUC并发)