# ReentrantReadWriteLock

# 场景

image.png 有多个线程对一个数据进行读写,一般是读多写少,只有写的时候,才会让共享数据改变,才会导致出现问题。 所以需要一种锁可以支持:写的时候都不能读,写完之后,都可以读。

# 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

# ReentrantReadWriteLock 的主要特点

  1. 读写分离:ReentrantReadWriteLock 提供了读锁和写锁,允许多个线程同时读取共享资源,但只允许一个线程写入共享资源。
  2. 重入性:ReentrantReadWriteLock 支持重入性,即同一个线程可以多次获取同一个锁,而不会被阻塞。
  3. 公平性:ReentrantReadWriteLock 支持公平和非公平两种模式。在公平模式下,锁的获取顺序与线程等待的时间有关,先等待的线程优先获取锁;在非公平模式下,锁的获取顺序与线程等待的时间无关,可能会导致某些线程一直无法获取锁。
  4. 性能:ReentrantReadWriteLock 在读操作非常频繁的情况下可以提高并发性能,但在写操作非常频繁的情况下,会导致读锁等待时间过长,从而降低读操作的性能。
  5. 可中断性:ReentrantReadWriteLock 提供了可中断的锁获取方法,即在等待锁的过程中,可以通过调用 Thread.interrupt()方法来中断线程的等待状态。
  6. 条件变量:ReentrantReadWriteLock 提供了 Condition 对象,可以用于线程之间的通信和同步。

总之,ReentrantReadWriteLock 提供了一种灵活、高效、可重入的锁机制,适用于读多写少的场景。在实际使用中,需要根据具体的业务场景选择适当的锁策略。

# ReentrantReadWriteLock VS ReentrantLock

ReentrantReadWriteLock 和 ReentrantLock 都是 Java 中的锁机制,它们的主要区别如下:

  1. 锁的类型:
    • ReentrantReadWriteLock 提供了读锁和写锁两种类型的锁,支持多个线程同时读取共享资源,但只允许一个线程写入共享资源;
    • 而 ReentrantLock 是一种互斥锁,同一时刻只允许一个线程获取锁。
  2. 锁的粒度:
    • ReentrantReadWriteLock 以更细的粒度来控制共享资源的访问,可以在读多写少的场景下提高并发性能;
    • 而 ReentrantLock 是一种粗粒度的锁,适用于竞争激烈的场景。
  3. 锁的可重入性:ReentrantReadWriteLock 和 ReentrantLock 都支持锁的可重入性,即同一个线程可以多次获取同一个锁。
  4. 公平性:ReentrantReadWriteLock 和 ReentrantLock 都支持公平和非公平两种模式。在公平模式下,锁的获取顺序与线程等待的时间有关,先等待的线程优先获取锁;在非公平模式下,锁的获取顺序与线程等待的时间无关,可能会导致某些线程一直无法获取锁。
  5. 性能:
    • 在读操作非常频繁的情况下,使用 ReentrantReadWriteLock 可以提高并发性能,但在写操作非常频繁的情况下,会导致读锁等待时间过长,从而降低读操作的性能。
    • 而在竞争激烈的场景下,ReentrantLock 的性能更好。

综上所述,ReentrantReadWriteLock 和 ReentrantLock 都是 Java 中常用的锁机制,适用于不同的业务场景。在实际使用中,需要根据具体的业务需求选择适当的锁机制。

  • 如果是读多写少的场景,可以使用 ReentrantReadWriteLock 提高并发性能;
  • 如果是竞争激烈的场景,可以使用 ReentrantLock 来提高并发性能。

简单版区别:

  • ReentrantLock:完全互斥
  • ReentrantReadWriteLock:读锁共享,写锁互斥