在Java中使用ReentrantReadWriteLock实现读写锁
在多线程场景下,如果共享资源存在大量的读操作,而写操作相对较少,使用普通的互斥锁会导致所有读操作也被串行执行,严重降低系统吞吐量。Java并发包中的ReentrantReadWriteLock提供了读写分离锁的能力,读锁是共享锁,写锁是排他锁,能够在保证线程安全的前提下,大幅提升读多写少场景下的并发性能。
ReentrantReadWriteLock核心特性
了解ReentrantReadWriteLock的核心特性,能帮助我们更合理地使用它:
- 支持读写分离:读读不互斥、读写互斥、写写互斥,读操作可以并发执行,不会被其他读操作阻塞。
- 可重入性:同一个线程可以重复获取读锁或写锁,避免死锁问题。
- 锁降级支持:线程持有写锁的情况下,可以获取读锁,之后释放写锁,完成锁降级,这个特性在需要保证数据一致性的场景下非常实用。
- 公平与非公平模式:默认是非公平模式,吞吐量更高;也可以构造时指定为公平模式,按照线程等待顺序获取锁,避免线程饥饿。
基础使用步骤
使用ReentrantReadWriteLock实现读写锁的基本流程如下:
- 创建
ReentrantReadWriteLock实例,根据需求选择公平或非公平模式。 - 通过实例的
readLock()方法获取读锁,writeLock()方法获取写锁。 - 在需要执行读操作的代码块前获取读锁,执行完成后在finally块中释放读锁。
- 在需要执行写操作的代码块前获取写锁,执行完成后在finally块中释放写锁。
完整代码示例
下面的示例模拟了一个简单的缓存场景,多个线程并发读取缓存数据,写线程会更新缓存,通过读写锁保证缓存操作的线程安全:
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockCache {
// 模拟缓存的Map
private final Map<String, String> cache = new HashMap<>();
// 创建非公平模式的读写锁
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
// 获取读锁和写锁
private final ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
private final ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
/**
* 读取缓存数据,使用读锁
* @param key 缓存键
* @return 缓存值,不存在返回null
*/
public String get(String key) {
readLock.lock();
try {
System.out.println(Thread.currentThread().getName() + " 获取读锁,读取key:" + key);
// 模拟读操作耗时
Thread.sleep(100);
return cache.get(key);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
} finally {
readLock.unlock();
System.out.println(Thread.currentThread().getName() + " 释放读锁");
}
}
/**
* 写入缓存数据,使用写锁
* @param key 缓存键
* @param value 缓存值
*/
public void put(String key, String value) {
writeLock.lock();
try {
System.out.println(Thread.currentThread().getName() + " 获取写锁,写入key:" + key + ",value:" + value);
// 模拟写操作耗时
Thread.sleep(200);
cache.put(key, value);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
writeLock.unlock();
System.out.println(Thread.currentThread().getName() + " 释放写锁");
}
}
/**
* 锁降级示例:先获取写锁更新数据,再获取读锁读取最新数据,最后释放写锁完成降级
* @param key 缓存键
* @param value 新的缓存值
* @return 更新后的缓存值
*/
public String updateWithLockDowngrade(String key, String value) {
writeLock.lock();
try {
System.out.println(Thread.currentThread().getName() + " 获取写锁,准备更新数据");
// 先更新数据
cache.put(key, value);
// 获取读锁,完成锁降级
readLock.lock();
// 此时写锁还未释放,持有写锁+读锁
String result = cache.get(key);
// 释放写锁,此时只持有读锁,完成锁降级
return result;
} finally {
// 先释放读锁,再释放写锁(如果写锁还没释放的话,这里先释放读锁)
readLock.unlock();
writeLock.unlock();
System.out.println(Thread.currentThread().getName() + " 完成锁降级,释放所有锁");
}
}
public static void main(String[] args) {
ReadWriteLockCache cache = new ReadWriteLockCache();
// 初始化缓存
cache.put("name", "初始值");
// 创建多个读线程
for (int i = 0; i < 3; i++) {
new Thread(() -> {
for (int j = 0; j < 2; j++) {
cache.get("name");
}
}, "读线程-" + i).start();
}
// 创建一个写线程
new Thread(() -> {
cache.put("name", "更新后的值");
}, "写线程").start();
// 测试锁降级
new Thread(() -> {
String result = cache.updateWithLockDowngrade("name", "锁降级后的值");
System.out.println(Thread.currentThread().getName() + " 锁降级后读取到的值:" + result);
}, "锁降级线程").start();
}
}运行上述代码可以发现,多个读线程可以几乎同时获取读锁执行读取操作,而写线程执行时,所有读线程都会被阻塞,直到写线程释放写锁,符合读写锁的互斥规则。锁降级的示例中,线程在持有写锁的情况下获取读锁,之后释放写锁,既能保证更新后立即读到最新数据,也不会长时间阻塞其他读线程。
使用注意事项
在实际使用ReentrantReadWriteLock时,需要注意以下几点:
- 锁的获取和释放必须成对出现,建议在finally块中执行释放锁的操作,避免因为异常导致锁无法释放,引发死锁。
- 不支持锁升级:线程持有读锁的情况下,不能直接获取写锁,会导致当前线程阻塞,甚至死锁,如果需要写操作,应该先释放读锁,再获取写锁。
- 读锁不支持条件变量:
ReentrantReadWriteLock的读锁没有提供newCondition()方法,只有写锁支持条件变量。 - 根据业务场景选择公平模式:如果业务对线程等待顺序有要求,可以选择公平模式,否则默认的非公平模式吞吐量更高。
Java并发编程ReentrantReadWriteLock读写分离锁锁降级线程安全修改时间:2026-05-24 14:05:44