基本使用方法
-
创建锁对象
首先,通过new ReentrantReadWriteLock()
创建一个锁实例。 -
获取读锁和写锁
使用readLock()
方法获得读锁对象,使用writeLock()
方法获得写锁对象。 -
使用锁保护共享资源
在需要保护的代码块前后分别调用lock()
和unlock()
方法,确保对共享资源的访问安全。
示例代码:
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockDemo {
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
// 分别获取读锁和写锁
private final ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
private final ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
private int sharedData = 0;
// 使用读锁保护读操作
public int read() {
readLock.lock();
try {
// 模拟读取操作
return sharedData;
} finally {
readLock.unlock();
}
}
// 使用写锁保护写操作
public void write(int value) {
writeLock.lock();
try {
// 模拟写入操作
sharedData = value;
} finally {
writeLock.unlock();
}
}
public static void main(String[] args) {
ReadWriteLockDemo demo = new ReadWriteLockDemo();
// 示例:一个线程进行写操作,其他线程进行读操作
new Thread(() -> {
demo.write(100);
System.out.println("写线程修改数据为 100");
}, "写线程").start();
new Thread(() -> {
int data = demo.read();
System.out.println("读线程读取数据:" + data);
}, "读线程1").start();
new Thread(() -> {
int data = demo.read();
System.out.println("读线程读取数据:" + data);
}, "读线程2").start();
}
}
读锁和写锁之间的互斥关系
-
读锁(共享锁)
- 多个线程可以同时获取读锁,只要当前没有线程持有写锁。
- 这意味着多个线程可以并发地执行读操作,提高了系统的并发性能。
-
写锁(排他锁)
- 写锁是互斥的,一次只允许一个线程获取写锁。
- 当一个线程持有写锁时,其他线程无论是尝试获取读锁还是写锁都会被阻塞,直到写锁被释放,从而确保写操作的独占性。
-
互斥规则
- 如果某个线程正在持有写锁,那么所有试图获取读锁和写锁的线程都会阻塞。
- 如果有线程持有读锁,那么试图获取写锁的线程将会等待所有读锁释放后才能获取写锁。这就是为什么在持有读锁的情况下不能直接升级为写锁(锁升级)——这可能导致死锁。
- 然而,锁降级(从写锁降级为读锁)是允许的,即在持有写锁的情况下可以获取读锁,再释放写锁,保持对共享资源的访问权
补充:
读锁是共享的:
多个线程可以同时持有读锁,只要没有线程持有写锁,这样可以允许并发读操作,从而提高系统并发性能。
“共享内存”指的是多个线程可以共享同一份数据的只读访问权限。
写锁是独占的:
一次只有一个线程可以持有写锁,并且在写锁持有期间,其他线程无论是请求读锁还是写锁都会被阻塞。
“独占内存”则意味着写操作必须保证独占访问,防止数据出现并发修改问题。
- 重入规则:
- 同一线程的写锁→读锁(可重入):
当一个线程已经持有写锁时,它可以再次获取读锁。这种情况称为锁降级(从写锁降级到读锁),允许线程在完成写操作后继续进行读操作,而不需要释放写锁后再重新获得读锁。 - 同一线程的读锁→写锁(不可重入):
如果一个线程已经持有读锁,然后尝试获取写锁。ReentrantReadWriteLock
不支持从读锁升级为写锁,因为这样做很容易引发死锁(例如,其他线程同时持有读锁时,该线程就无法获得写锁)。
- 同一线程的写锁→读锁(可重入):