# ReentrantReadWriteLock
# 场景
有多个线程对一个数据进行读写,一般是读多写少,只有写的时候,才会让共享数据改变,才会导致出现问题。 所以需要一种锁可以支持:写的时候都不能读,写完之后,都可以读。
# ReentrantReadWriteLock 介绍
ReentrantReadWriteLock 是 Java 中的一种锁,它提供了 读写分离的锁机制。它允许多个线程同时读取共享资源,但只有一个线程可以写入共享资源。与普通的互斥锁相比,它可以提高并发性能。 ReentrantReadWriteLock 有两种锁:读锁和写锁。
- 读锁(ReadLock,也被称为 共享锁):可以被多个线程同时持有,只要没有线程持有写锁。
- 写锁(WriteLock也被称为 排他锁):必须独占整个锁,并且在写锁未被释放之前,所有的读锁和写锁都会被阻塞。
此外,ReentrantReadWriteLock 还支持重入性,即同一个线程可以多次获取同一个锁,而不会被阻塞。 ReentrantReadWriteLock 适用于读多写少的场景,因为在读操作非常频繁的情况下,使用互斥锁会导致性能瓶颈。但是,在写操作非常频繁的情况下,使用 ReentrantReadWriteLock 会导致读锁等待时间过长,从而降低读操作的性能。
总结如下:
- 线程获取读锁的条件:
- 当前没有任何线程持有写锁时,可以直接获取读锁。
- 当前线程已经持有写锁,可以直接再次获取读锁。
- 如果有其他线程持有写锁,当前线程必须等待,直到所有的写锁都被释放,才能获取读锁。
- 线程获取写锁的条件:
- 当前没有任何线程持有写锁或读锁时,可以直接获取写锁。
- 当前线程已经持有写锁,可以直接再次获取写锁。
- 当前线程已经持有读锁,需要先释放读锁,然后再获取写锁。
- 如果有其他线程持有读锁或写锁,当前线程必须等待,直到所有的读锁和写锁都被释放,才能获取写锁。(独占锁的体现)
# 示例
下面的例子可以体现出前面说的读写锁的获得条件的体现
package cn.mrcode.demo.boodadmin.lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @author mrcode
* @date 2023/3/6 20:57
*/
public class ReentrantReadWriteLockTest1 {
// 共享数据
private Object data;
// 缓存是否有效
private volatile boolean cacheValid;
private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
public void processCachedData() {
// 获得读锁 1
rwl.readLock().lock();
// 如果缓存无效,更新 cache;否则执行使用缓存 data
if (!cacheValid) {
// 释放当前线程开始获取的读锁 1
// 获取写锁前需释放读锁: 独占锁的体现
rwl.readLock().unlock();
// 或得写锁 2
rwl.writeLock().lock();
// 双重判断
if (!cacheValid) {
data = new Object();
cacheValid = true;
}
// 锁降级,在释放写锁前获取读锁
// 获得写锁之后,还可以获得读锁 3
rwl.readLock().lock();
// 释放写锁 2,依然持有读锁 3
rwl.writeLock().unlock();
}
// 使用缓存
// ...
// 释放读锁
// 这里有可能是释放的读锁 1,或则是释放的是读锁 3
rwl.readLock().unlock();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# ReentrantReadWriteLock 的主要特点
- 读写分离:ReentrantReadWriteLock 提供了读锁和写锁,允许多个线程同时读取共享资源,但只允许一个线程写入共享资源。
- 重入性:ReentrantReadWriteLock 支持重入性,即同一个线程可以多次获取同一个锁,而不会被阻塞。
- 公平性:ReentrantReadWriteLock 支持公平和非公平两种模式。在公平模式下,锁的获取顺序与线程等待的时间有关,先等待的线程优先获取锁;在非公平模式下,锁的获取顺序与线程等待的时间无关,可能会导致某些线程一直无法获取锁。
- 性能:ReentrantReadWriteLock 在读操作非常频繁的情况下可以提高并发性能,但在写操作非常频繁的情况下,会导致读锁等待时间过长,从而降低读操作的性能。
- 可中断性:ReentrantReadWriteLock 提供了可中断的锁获取方法,即在等待锁的过程中,可以通过调用
Thread.interrupt()
方法来中断线程的等待状态。 - 条件变量:ReentrantReadWriteLock 提供了 Condition 对象,可以用于线程之间的通信和同步。
总之,ReentrantReadWriteLock 提供了一种灵活、高效、可重入的锁机制,适用于读多写少的场景。在实际使用中,需要根据具体的业务场景选择适当的锁策略。
# ReentrantReadWriteLock VS ReentrantLock
ReentrantReadWriteLock 和 ReentrantLock 都是 Java 中的锁机制,它们的主要区别如下:
- 锁的类型:
- ReentrantReadWriteLock 提供了读锁和写锁两种类型的锁,支持多个线程同时读取共享资源,但只允许一个线程写入共享资源;
- 而 ReentrantLock 是一种互斥锁,同一时刻只允许一个线程获取锁。
- 锁的粒度:
- ReentrantReadWriteLock 以更细的粒度来控制共享资源的访问,可以在读多写少的场景下提高并发性能;
- 而 ReentrantLock 是一种粗粒度的锁,适用于竞争激烈的场景。
- 锁的可重入性:ReentrantReadWriteLock 和 ReentrantLock 都支持锁的可重入性,即同一个线程可以多次获取同一个锁。
- 公平性:ReentrantReadWriteLock 和 ReentrantLock 都支持公平和非公平两种模式。在公平模式下,锁的获取顺序与线程等待的时间有关,先等待的线程优先获取锁;在非公平模式下,锁的获取顺序与线程等待的时间无关,可能会导致某些线程一直无法获取锁。
- 性能:
- 在读操作非常频繁的情况下,使用 ReentrantReadWriteLock 可以提高并发性能,但在写操作非常频繁的情况下,会导致读锁等待时间过长,从而降低读操作的性能。
- 而在竞争激烈的场景下,ReentrantLock 的性能更好。
综上所述,ReentrantReadWriteLock 和 ReentrantLock 都是 Java 中常用的锁机制,适用于不同的业务场景。在实际使用中,需要根据具体的业务需求选择适当的锁机制。
- 如果是读多写少的场景,可以使用 ReentrantReadWriteLock 提高并发性能;
- 如果是竞争激烈的场景,可以使用 ReentrantLock 来提高并发性能。
简单版区别:
- ReentrantLock:完全互斥
- ReentrantReadWriteLock:读锁共享,写锁互斥