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应用
应用介绍
使用读写锁保证缓存一致性