ReentrantReadWriteLock
是 Java 并发包(java.util.concurrent.locks)中提供的一种读写锁实现,它提供了一种高效的读写分离锁机制,允许多个线程同时读取共享资源,但只允许一个线程进行写操作,从而在保证数据一致性的前提下,提高了并发访问的效率。
ReentrantReadWriteLock 的特点
- 重入性:该锁支持重入。读线程可以重复获取读锁,写线程也可以重复获取写锁。同时,写线程获取写锁后也能获取读锁,但是读线程拥有读锁后不能升级为写锁。
- 公平性选择:构造函数接受一个布尔值,来决定锁是公平的还是非公平的。公平锁意味着等待时间最长的线程会优先得到锁。
- 锁降级:写线程持有写锁状态下可以获取读锁,进而释放写锁,这种机制叫做锁降级,可以缩小数据不一致的窗口期。
- 读写分离:多个线程可以同时持有读锁,而写锁是独占的。这种读写分离的设计能在很多场景下提高性能,尤其是读多写少的场景。
如何使用
ReentrantReadWriteLock
包含两个主要部分:ReadLock
和 WriteLock
。在操作数据前,需要根据数据的读写操作获取对应的锁。
ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
Lock readLock = readWriteLock.readLock();
Lock writeLock = readWriteLock.writeLock();
// 获取读锁进行读取操作
readLock.lock();
try {
// 执行读取操作
} finally {
readLock.unlock();
}
// 获取写锁进行写入操作
writeLock.lock();
try {
// 执行写入操作
} finally {
writeLock.unlock();
}
源码分析
ReentrantReadWriteLock
内部通过使用两个类:ReadLock
和 WriteLock
来分别管理读锁状态和写锁状态。锁状态是通过维护一个整数(一个 32 位的 int
)来实现的,其中高 16 位代表读状态,低 16 位代表写状态。
写锁 的获取和释放都要先检查读锁状态,保证没有线程在执行读操作时才能获取写锁。写锁是独占的,一旦写锁被获取,其他线程无法获取读锁或写锁。
读锁 的获取则允许在没有写锁或者当前线程持有写锁时成功。这意味着读锁是可以与其他读锁共享的,但一旦有线程请求写锁,新来的读锁必须等待,以避免写锁饿死。
关于线程的等待队列,ReentrantReadWriteLock
按公平和非公平方式分别维护了两个队列。公平锁会在队列中严格按照等待时间来分配锁,而非公平锁则可能会允许在队列外的线程抢到锁,这会降低上下文切换的开销从而提升效率,但同时也带来了潜在的公平性问题。
适用场景
ReentrantReadWriteLock
是为读多写少的并发场景而设计的。在读操作远远多于写操作的情况下,使用读写锁可以比使用独占锁(如 ReentrantLock
)显著提高并发性能。
写操作期间不允许任何读操作的发生,若写操作很少但耗时,最好是快速完成以避免长时间阻塞读操作。
结论
ReentrantReadWriteLock
主要用于实现高性能的并发读取,而在写操作相对较少的场景中表现尤为突出。它保证了数据的一致性和线程安全,在合适的场合合理使用 ReentrantReadWriteLock
,可以实现更加细粒度的控制,并显著提升应用性能。然而,需要注意它的复杂度较一般的互斥锁高,因此在选择使用时要仔细考虑其适用场景。