读写锁机制的基本概念
在编写多线程程序时,经常会遇到多个线程同时访问同一份数据的情况。比如一个在线文档协作系统,很多人可以查看文档(读操作),但只有少数人能编辑(写操作)。如果不管控访问顺序,就可能出现数据错乱。这时候,读写锁就派上用场了。
读写锁是一种同步机制,它允许多个线程同时读取共享资源,但只允许一个线程写入,并且写入时禁止任何读操作。这种设计比普通的互斥锁更灵活,提升了并发性能。
读锁与写锁的分工
读锁是“共享锁”,多个线程可以同时持有读锁进行读操作。就像图书馆里多人同时翻看同一本书,不会影响内容。而写锁是“独占锁”,一旦某个线程开始修改数据,其他所有线程,无论是想读还是想写,都得排队等待。
举个生活中的例子:你家冰箱贴了一张购物清单,家人可以随时看看买了啥(读操作),但只要有人开始修改清单内容(写操作),其他人就得等他写完才能继续看或改,避免信息冲突。
读写锁的工作流程
当一个线程请求读锁时,系统会检查是否有写锁正在被占用。如果没有,就允许该线程获得读锁并继续执行。如果有多个线程都在读,它们可以并行工作。
而当一个线程请求写锁时,系统必须确保当前没有任何读锁或写锁存在。只有在所有读线程都释放锁之后,写线程才能拿到锁,开始修改数据。写操作完成后,锁被释放,等待队列中的读或写请求再按规则获取。
代码示例:简单的读写锁使用
以 Java 中的 ReentrantReadWriteLock 为例:
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class DataCache {
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private String data = "default";
public String read() {
lock.readLock().lock(); // 获取读锁
try {
return data;
} finally {
lock.readLock().unlock(); // 释放读锁
}
}
public void write(String newData) {
lock.writeLock().lock(); // 获取写锁
try {
data = newData;
} finally {
lock.writeLock().unlock(); // 释放写锁
}
}
}在这个例子中,多个线程可以同时调用 read() 方法读取数据,但只要有一个线程在执行 write(),其他所有读和写操作都会被阻塞。
读写锁的适用场景
读写锁特别适合“读多写少”的场景。比如配置文件缓存、数据库查询缓存、网页静态资源加载器等。在这些情况下,大多数操作是读取已有数据,偶尔才需要更新,使用读写锁可以显著提升系统吞吐量。
但如果写操作频繁,读写锁的优势就不明显了,甚至可能因为锁竞争加剧导致性能下降。这时候可能需要考虑其他并发控制策略。
潜在问题与注意事项
虽然读写锁提高了并发效率,但也带来一些需要注意的问题。比如“写饥饿”现象:如果读线程持续不断进入,写线程可能一直得不到执行机会。有些实现会通过优先让写线程排队来缓解这个问题。
另外,不当嵌套使用读写锁可能导致死锁。例如在一个已持有读锁的方法中尝试获取写锁,就会造成自己等自己,程序卡住。