ReentrantReadWriteLock 笔记及读写者问题实现

一、ReentrantReadWriteLock 核心概念

ReentrantReadWriteLock 是 Java 并发包中用于解决读写者问题的同步工具,基于「读写分离锁」设计,包含两把锁:

  • 读锁(SharedLock):允许多个线程同时获取,适合读操作(共享资源)。
  • 写锁(ExclusiveLock):仅允许一个线程获取,适合写操作(独占资源)。

其核心特性是读写互斥、写写互斥、读读共享,完美满足读写者问题的 3 个基本要求。

二、锁的获取条件

  1. 获取读锁的前提

    • 没有线程持有写锁;
    • 若有写请求,仅当当前线程是持有写锁的线程时(可重入)才能获取读锁。
  2. 获取写锁的前提

    • 没有线程持有读锁;
    • 没有线程持有写锁;
    • 当前线程可重入(已持有写锁时可再次获取)。

三、公平性策略

ReentrantReadWriteLock 支持两种模式(通过构造函数指定):

  • 非公平模式(默认):读操作可能优先于写操作(读者可能持续抢占,导致写者饥饿)。
  • 公平模式:按线程请求顺序获取锁(实现「弱写者优先 / 公平竞争」,避免饥饿)。

四、核心方法

方法 说明
readLock() 获取读锁(Lock 接口实例)
writeLock() 获取写锁(Lock 接口实例)
getReadLockCount() 当前持有读锁的线程数(重入会累加)
isWriteLocked() 判断写锁是否被持有

五、公平竞争模式下的读写者问题实现

以下代码通过 ReentrantReadWriteLock 的公平模式,实现「弱写者优先(公平竞争)」的读写者问题。

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.Lock;

/**
* 基于 ReentrantReadWriteLock 实现公平竞争的读写者问题
*/
public class ReadWriteDemo {
// 共享资源(模拟需要读写的数据)
private int data = 0;
// 公平模式的读写锁(true 表示公平)
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(true);
// 读锁(共享)
private final Lock readLock = rwLock.readLock();
// 写锁(独占)
private final Lock writeLock = rwLock.writeLock();

// 读操作:获取读锁,读取数据
public int read() {
readLock.lock(); // 获取读锁
try {
System.out.println(Thread.currentThread().getName() + " 开始读数据,当前数据:" + data);
// 模拟读操作耗时
Thread.sleep(500);
return data;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return -1;
} finally {
System.out.println(Thread.currentThread().getName() + " 读完数据");
readLock.unlock(); // 释放读锁
}
}

// 写操作:获取写锁,修改数据
public void write(int newData) {
writeLock.lock(); // 获取写锁
try {
System.out.println(Thread.currentThread().getName() + " 开始写数据,新数据:" + newData);
// 模拟写操作耗时
Thread.sleep(1000);
data = newData;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
System.out.println(Thread.currentThread().getName() + " 写完数据");
writeLock.unlock(); // 释放写锁
}
}

public static void main(String[] args) {
ReadWriteDemo demo = new ReadWriteDemo();

// 创建 3 个读者线程
for (int i = 0; i < 3; i++) {
new Thread(() -> {
while (true) { // 持续读
demo.read();
try {
Thread.sleep(1000); // 读间隔
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "Reader-" + i).start();
}

// 创建 2 个写者线程
for (int i = 0; i < 2; i++) {
final int num = i;
new Thread(() -> {
int value = 0;
while (true) { // 持续写
demo.write(value + num * 100); // 写不同的数据区分
value++;
try {
Thread.sleep(2000); // 写间隔
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "Writer-" + i).start();
}
}
}

六、代码说明

  1. 共享资源data 变量模拟需要并发读写的数据。
  2. 锁初始化ReentrantReadWriteLock(true) 表示公平模式,线程按请求顺序获取锁。
  3. 读操作:通过 readLock 保证多个读者可同时读取,读取时阻塞写者。
  4. 写操作:通过 writeLock 保证独占写入,写入时阻塞所有读者和其他写者。
  5. 公平性体现:写者不会被读者持续抢占(非公平模式可能出现),符合「公平竞争」要求。

七、注意事项

  • 重入性:读锁和写锁都支持重入(同一线程可多次获取),但读锁不能升级为写锁(避免死锁)。
  • 性能:读操作频繁时,读写锁效率远高于 ReentrantLock(减少锁竞争);写操作频繁时,优势不明显。
  • 饥饿问题:非公平模式可能导致写者饥饿,公平模式通过排队解决,但性能略低。

通过 ReentrantReadWriteLock,可简洁高效地解决读写者问题的各种场景(读者优先、公平竞争)。