2022-09-25并发编程系列00
请注意,本文编写于 578 天前,最后修改于 578 天前,其中某些信息可能已经过时。

b.jpg

ReentrantLock读写锁

ReentrantReadWriteLock

基本介绍

当读操作远远高于写操作时,这时候使用 读写锁 让 读-读 可以并发,提高性能。 类似于数据库中的 select ... from ... lock in share mode 提供一个数据容器类DataContainer内部分别使用读锁保护数据的 read() 方法,写锁保护数据的 write() 方法

需要注意的是 :

  • 读-读 是可以并发执行的
  • 写-写读-写 以及 写-读 都是互相阻塞的

读写锁的使用

DataContainer

提供一个 数据容器类 内部分别使用读锁保护数据的 read() 方法,写锁保护数据的 write() 方法

package cn.knightzz.juc.reentrantlock.readwrite;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @author 王天赐
 * @title: DataContainer
 * @projectName hm-juc-codes
 * @description:
 * @website <a href="http://knightzz.cn/">http://knightzz.cn/</a>
 * @github <a href="https://github.com/knightzz1998">https://github.com/knightzz1998</a>
 * @create: 2022-09-19 15:23
 */
@SuppressWarnings("all")
@Slf4j(topic = "c.DataContainer")
public class DataContainer {

    private Object data;
    private ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
    private ReentrantReadWriteLock.ReadLock readLock = rw.readLock();
    private ReentrantReadWriteLock.WriteLock writeLock = rw.writeLock();

    public Object read() {

        // 开启读锁
        log.debug("获取读取锁 ... ");
        readLock.lock();
        try {
            log.debug("开始读取 ... ");
            TimeUnit.SECONDS.sleep(1);
            return data;
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            log.debug("释放读锁 ... ");
            readLock.unlock();
        }
    }

    public void write() {
        log.debug("获取写入锁..");
        writeLock.lock();
        try {
            log.debug("开始写入数据 ... ");
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            log.debug("释放写入锁 ... ");
            writeLock.unlock();
        }
    }

}

ReadWriteLockTest

package cn.knightzz.juc.reentrantlock.readwrite;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

import java.io.IOException;

/**
 * @author 王天赐
 * @title: ReadWriteLockTest
 * @projectName hm-juc-codes
 * @description:
 * @website <a href="http://knightzz.cn/">http://knightzz.cn/</a>
 * @github <a href="https://github.com/knightzz1998">https://github.com/knightzz1998</a>
 * @create: 2022-09-19 14:37
 */
@SuppressWarnings("all")
@Slf4j(topic = "c.ReadWriteLockTest")
public class ReadWriteLockTest {

    public static void main(String[] args) throws IOException, InterruptedException {
        log.debug("==========================={}=========================", "测试读锁并发");
        // 只有读锁是可以多个线程同时执行的
        DataContainer container = new DataContainer();
        for (int i = 1; i < 5; i++) {
            new Thread(() -> {
                container.read();
            }, "t" + i).start();
        }

        System.in.read();

        log.debug("==========================={}=========================", "测试写锁并发");
        // 写入 和 读写都是相互阻塞的, 只有一方释放锁, 另一方才可以获取对应的锁去执行
        for (int i = 1; i < 5; i++) {
            new Thread(() -> {
                container.write();
            }, "t" + i).start();
        }

        System.in.read();

        log.debug("==========================={}=========================", "测试读写锁相互阻塞");
        new Thread(() -> {
            container.read();
        }, "t1").start();

        // sleep 100ms
        Thread.sleep(100);

        new Thread(() -> {
            container.write();
        }, "t2").start();

        System.in.read();
    }
}

读并发测试 :

19:16:10.757 [main] DEBUG c.ReadWriteLockTest - ===========================测试读锁并发=========================
19:16:10.791 [t1] DEBUG c.DataContainer - 获取读取锁 ... 
19:16:10.792 [t2] DEBUG c.DataContainer - 获取读取锁 ... 
19:16:10.792 [t3] DEBUG c.DataContainer - 获取读取锁 ... 
19:16:10.792 [t3] DEBUG c.DataContainer - 开始读取 ... 
19:16:10.792 [t4] DEBUG c.DataContainer - 获取读取锁 ... 
19:16:10.792 [t1] DEBUG c.DataContainer - 开始读取 ... 
19:16:10.792 [t2] DEBUG c.DataContainer - 开始读取 ... 
19:16:10.792 [t4] DEBUG c.DataContainer - 开始读取 ... 
19:16:11.796 [t1] DEBUG c.DataContainer - 释放读锁 ... 
19:16:11.796 [t2] DEBUG c.DataContainer - 释放读锁 ... 
19:16:11.796 [t4] DEBUG c.DataContainer - 释放读锁 ... 
19:16:11.796 [t3] DEBUG c.DataContainer - 释放读锁 ... 

可以看到上面执行结果 : 开始读取的时间几乎是同一时间, 可以说明他们是同时获取锁的,

写并发测试 :

19:16:15.580 [main] DEBUG c.ReadWriteLockTest - ===========================测试写锁并发=========================
19:16:15.581 [t1] DEBUG c.DataContainer - 获取写入锁..
19:16:15.581 [t2] DEBUG c.DataContainer - 获取写入锁..
19:16:15.581 [t1] DEBUG c.DataContainer - 开始写入数据 ... 
19:16:15.581 [t3] DEBUG c.DataContainer - 获取写入锁..
19:16:15.581 [t4] DEBUG c.DataContainer - 获取写入锁..
19:16:16.587 [t1] DEBUG c.DataContainer - 释放写入锁 ... 
19:16:16.587 [t2] DEBUG c.DataContainer - 开始写入数据 ... 
19:16:17.602 [t2] DEBUG c.DataContainer - 释放写入锁 ... 
19:16:17.602 [t3] DEBUG c.DataContainer - 开始写入数据 ... 
19:16:18.606 [t3] DEBUG c.DataContainer - 释放写入锁 ... 
19:16:18.606 [t4] DEBUG c.DataContainer - 开始写入数据 ... 
19:16:19.610 [t4] DEBUG c.DataContainer - 释放写入锁 ... 

可以看到 t1 获取锁后开始写入数据, 此时有其他线程都在尝试获取锁, 但是都没有成功, 在t1释放锁后, t3线程获取到锁开始写入数据

读写并发

19:16:21.075 [main] DEBUG c.ReadWriteLockTest - ===========================测试读写锁相互阻塞=========================
19:16:21.076 [t1] DEBUG c.DataContainer - 获取读取锁 ... 
19:16:21.076 [t1] DEBUG c.DataContainer - 开始读取 ... 
19:16:21.185 [t2] DEBUG c.DataContainer - 获取写入锁..
19:16:22.085 [t1] DEBUG c.DataContainer - 释放读锁 ... 
19:16:22.085 [t2] DEBUG c.DataContainer - 开始写入数据 ... 
19:16:23.091 [t2] DEBUG c.DataContainer - 释放写入锁 ... 

可以看到上面的运行结果, t1线程先获取读锁, 此时t2线程也尝试获取写入锁, 但是等到1s后t1读取完成并释放锁后, t2线程才开始写入数据.

条件变量

  • 读锁不支持条件变量
  • 重入时升级不支持:即持有读锁的情况下去获取写锁,会导致获取写锁永久等待

ReentrantReadWriteLock应用

应用介绍

使用读写锁保证缓存一致性

本文作者:王天赐

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!