ReadWriteLock under JUC

Preface

In the code, for the locked block, when the call is executed by multiple threads, it will be executed first according to the lock acquired first, and other threads must wait for it to successfully release the lock before continuing to use the resource.

Although the integrity of the data is guaranteed on the whole, the efficiency will be reduced. In response to this problem, there is a read-write lock class in the JUC package ReadWriteLock.

Introduction

ReadWriteLockIt is one in java 接口. There is one and only one officially defined subclass java.util.concurrent.locks.ReentrantReadWriteLock.
Insert picture description here
According JDK 1.8to the introduction in the development document, for 读锁operations, multiple threads are allowed to execute at the same time; for write operations, only threads are allowed to execute sequentially.
Insert picture description here

Let's look at a chestnut below.

Case study

Without lock

Multi-threaded operation of a cache class, 同时execution 添加, 获取operation, the code case is as follows:

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");
    }
}

The running log is as follows:
Insert picture description here
Insert picture description here

Without locking, adding data to the cache will always cause other threads to jump in the queue.

Use ReentrantLock to lock

In order to ensure that other threads will not interfere with the operation of writing data when it is executed, it is necessary to add a lock in the setsum getmethod to ensure sequential execution.

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();
        }

    }
}

After execution, the console log print information is as follows:
Insert picture description here

Both read and write operations can print add (get) data and add (get) ok,

However, the read operation is also locked. When only the read operation is executed, there is no need to lock at this time, and the lock will affect the execution efficiency.

[疑问:]At this point, someone may ask:

Since the read operation adds a lock and affects the efficiency, then the read operation does not add a lock.

But combined with the previous explanation 八锁效应, the read operation is not locked and the write operation is locked, then the read operation will be queued to the write operation!

ReentrantReadWriteLock read-write lock

In order to ensure the sequence of data writing, it must be locked and unlocked in the following states:

  • Read-read operation:
    read operation, just get data from the Map collection, return data information if there is data, and return null if there is no data. There is no concurrency problem, so不需要加锁
  • Read-write operation:
    Considering that multiple threads are executed at the same time, the read operation is unlocked, but the write operation must be locked to ensure that the data operation is safe; combined, it must be ensured that the 读写operation is performed at the same time 需要加锁.
  • Write-write operation
    Between write and write operations, security issues must be ensured during the execution of each thread operation 需要加锁.

Use ReentrantReadWriteLockcan be the perfect solution to the above problems.

Look at the following chestnuts:

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();
        }
    }
}

Insert picture description here
Insert picture description here
[发现:]
1. When it is set to 延迟时间ensure that the write operation and the read operation are executed separately. The log is as follows:
Insert picture description here
Insert picture description here

Write operations will have a one-to-one correspondence (write and write ok)!
The read operation will not correspond!
Read-write lock, write-write will lock, read-read will not lock.

2. Cancel the delay, test write-read
Insert picture description here

Even if there is a read operation in the write operation, but the log 操作执行and 执行okyes 一一对应, it shows 写-读that there is a lock in the operation!

Guess you like

Origin blog.csdn.net/qq_38322527/article/details/114944308