JUC下的读写锁 ReadWriteLock

前言

代码中,针对加锁的区块,多线程执行调用时,会根据先获取到锁先执行,其他线程必须等待其成功释放锁后才能继续使用资源。

整体而言虽然保证了数据的完整性,但是对于效率来说,会有所降低。针对此问题,在JUC包下存在一个读写锁类ReadWriteLock

简介

ReadWriteLock在java中是一个接口。有且仅有一个官方定义的子类java.util.concurrent.locks.ReentrantReadWriteLock
在这里插入图片描述
JDK 1.8开发文档中的介绍得知,针对读锁操作,允许多个线程同时执行;针对写操作,只允许线程依次执行。
在这里插入图片描述

下面看一个栗子。

案例

无锁情况下

多线程操作一个缓存类,同时执行添加获取操作,代码案例如下所示:

package demo5_2;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

public class NoLockTest {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        // 缓存只能单例,让多个线程同时操作一个资源,才会有并发安全问题
        MyNoLockCache myNoLockCache = new MyNoLockCache();
        // 1、开启多个线程进行写操作
        for (int i = 1; i <= 20 ; i++) {
    
    
            // 临时变量
            final int temp = i;
            new Thread(()->{
    
    
                myNoLockCache.set(String.valueOf(temp),"66666");
            },String.valueOf(i)).start();
        }
        TimeUnit.SECONDS.sleep(4);
        // 2、多个线程读操作
        for (int i = 1; i <= 20 ; i++) {
    
    
            // 临时变量
            final int temp = i;
            new Thread(()->{
    
    
                myNoLockCache.get(String.valueOf(temp));
            },String.valueOf(i)).start();
        }
    }
    /**
     * 没有锁的时候,设置总会被插队,导致日志打印不整齐;<br />
     */
}
/**
 * 自定义缓存类,实现数据的  保存  和 获取操作
 */
class MyNoLockCache{
    
    
    // 由于需要保存数据,此处采取集合的方式存储
    private  Map<String,Object> maps = new HashMap<>();

    public void set(String key,Object value){
    
    
        System.out.println("当前为  "+key+" 进行数据存储");
        maps.put(key,value);
        System.out.println("当前为  "+key+" 数据保存 OK");
    }

    public void get(String key){
    
    
        System.out.println(key+" 获取数据");
        maps.get(key);
        System.out.println(key+" 获取数据  OK");
    }
}

其中运行日志如下所示:
在这里插入图片描述
在这里插入图片描述

没有加锁的情况下,添加数据至缓存中,总会出现其他线程插队的现象。

使用 ReentrantLock 加锁

为了保证写入数据操作执行时,其他线程不会对其进行干扰操作,此时需要在setget方法中添加锁,保证顺序执行。

package demo5_2;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockTest {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        // 缓存只能单例,让多个线程同时操作一个资源,才会有并发安全问题
        MyLockCache myLockCache = new MyLockCache();
        // 1、开启多个线程进行写操作
        for (int i = 1; i <= 30 ; i++) {
    
    
            // 临时变量
            final int temp = i;
            new Thread(()->{
    
    
                myLockCache.set(String.valueOf(temp),"66666");
            },String.valueOf(i)).start();
        }
        // 这里的时间只是为了测试,时间越长只是将读和写分开。
        // 测试读、写操作交替执行导致的问题现象可以频闭此延迟!
        TimeUnit.SECONDS.sleep(4);
        // 2、多个线程读操作
        for (int i = 1; i <= 30 ; i++) {
    
    
            // 临时变量
            final int temp = i;
            new Thread(()->{
    
    
                myLockCache.get(String.valueOf(temp));
            },String.valueOf(i)).start();
        }
    }
}

/**
 * 自定义缓存类,实现数据的  保存  和 获取操作
 */
class MyLockCache{
    
    
    // 由于需要保存数据,此处采取集合的方式存储
    private  Map<String,Object> maps = new HashMap<>();

    // 生成对象资源的时候  就创建一把锁
    private Lock lock = new ReentrantLock();

    public void set(String key,Object value){
    
    
        this.lock.lock();
        try {
    
    
            System.out.println("当前为  "+key+" 进行数据存储");
            maps.put(key,value);
            System.out.println("当前为  "+key+" 数据保存 OK");
        }finally {
    
    
            this.lock.unlock();
        }
    }

    public void get(String key){
    
    
        // 当读操作不加锁,会在写操作中插队!
        this.lock.lock();
        try {
    
    
            System.out.println(key+" 获取数据");
            Object o = maps.get(key);
            System.out.println(key+" 获取数据  OK  = "+o);
        }finally {
    
    
            this.lock.unlock();
        }

    }
}

执行后,控制台日志打印信息如下所示:
在这里插入图片描述

读写操作都能打印添加(获取)数据和添加(获取)ok,

但是,读操作也进行了加锁,当只是读操作执行,此时并不需要进行加锁,加了锁反而影响了执行效率。

[疑问:]到这里可能有人会问:

既然读操作加了锁影响了效率,那读操作就不加锁嘛。

但是结合之前解释的八锁效应,读操作不加锁和写操作加锁,那么读操作会插队至写操作!

ReentrantReadWriteLock 读写锁

为了保证数据写入顺序性,必须要求在以下状态下加锁和无锁:

  • 读 - 读 操作:
    读操作,只是从Map集合中获取数据,有就返回数据信息,无数据则返回null。并无并发问题,所以不需要加锁
  • 读 - 写 操作:
    考虑到多个线程同时执行,读操作无锁,但写操作必须加锁保证数据操作安全行;结合来看就必须保证同时进行读写操作需要加锁
  • 写 - 写 操作
    写和写操作之间,必须保证每个线程操作执行时的安全问题,需要加锁

使用 ReentrantReadWriteLock就能完美解决上述问题。

看下面栗子:

package demo5_2;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockTest {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        // 缓存只能单例,让多个线程同时操作一个资源,才会有并发安全问题
        MyRWLock myRWLock = new MyRWLock();
        // 1、开启多个线程进行写操作
        for (int i = 1; i <= 10 ; i++) {
    
    
            // 临时变量
            final int temp = i;
            new Thread(()->{
    
    
                myRWLock.set(String.valueOf(temp),"66666");
            },String.valueOf(i)).start();
        }
        // 时间的长短  取决于测试  写-读 还是 单读,可以分别设定不同时间测试效果
        TimeUnit.SECONDS.sleep(2);
        // 2、多个线程读操作
        for (int i = 1; i <= 10 ; i++) {
    
    
            // 临时变量
            final int temp = i;
            new Thread(()->{
    
    
                myRWLock.get(String.valueOf(temp));
            },String.valueOf(i)).start();
        }
    }
}
class MyRWLock{
    
    
    // 由于需要保存数据,此处采取集合的方式存储
    private Map<String,Object> maps = new HashMap<>();
    // ReentrantReadWriteLock 创建读写锁
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock() ;

    public void set(String key,Object value){
    
    
        // 创建写锁
        this.readWriteLock.writeLock().lock();
        try {
    
    
            System.out.println("当前为  "+key+" 进行数据存储");
            maps.put(key,value);
            System.out.println("当前为  "+key+" 数据保存 OK");
        }finally {
    
    
            this.readWriteLock.writeLock().unlock();
        }
    }

    public void get(String key){
    
    
        // 创建读锁
        this.readWriteLock.readLock().lock();
        try {
    
    
            System.out.println(key+" 获取数据");
            maps.get(key);
            System.out.println(key+" 获取数据  OK");
        }finally {
    
    
            this.readWriteLock.readLock().unlock();
        }
    }
}

在这里插入图片描述
在这里插入图片描述
[发现:]
1、当设置延迟时间保证写操作和读操作分开执行时。日志如下:
在这里插入图片描述
在这里插入图片描述

写操作会有顺序(写和写ok)一一对应!
读操作则不会对应!
读写锁,写-写会加锁、读-读不会有锁。

2、取消延迟,测试写-读
在这里插入图片描述

即使出现写操作中插队有读操作,但日志操作执行执行ok一一对应的,说明在写-读操作中存在锁!

猜你喜欢

转载自blog.csdn.net/qq_38322527/article/details/114944308