返回

ReentrantReadWriteLock 源码分析

后端

作为 Java 并发库重要组成部分,ReentrantReadWriteLock 是并发编程中常使用的一种读写锁,它允许多个线程同时对共享资源进行读取操作,但在同一时刻只有一个线程可以进行写入操作。它提供了比传统互斥锁更好的性能,特别是当读操作远多于写操作时。

ReentrantReadWriteLock 的优势在于,可以实现多线程同时对同一个共享资源进行读取操作,从而提高并发性。同时,它通过对读操作和写操作进行分离,保证了写操作的互斥性,避免了读写冲突。此外,ReentrantReadWriteLock 还支持可重入,即同一个线程可以多次获取同一把锁,而不会造成死锁。

实现原理

ReentrantReadWriteLock 的实现基于 AQS(AbstractQueuedSynchronizer),AQS 是 Java 并发库中常用的锁实现框架,提供了基本的可重入锁功能。ReentrantReadWriteLock 对 AQS 进行了一些扩展,实现了读写锁的功能。

ReentrantReadWriteLock 中有两个队列:读队列和写队列。读队列用于保存等待获取读锁的线程,写队列用于保存等待获取写锁的线程。ReentrantReadWriteLock 使用一个名为 state 的变量来表示锁的状态,state 的值可以是 0、1 或 2,分别表示没有线程持有锁、有线程持有读锁或有线程持有写锁。

当一个线程试图获取读锁时,它会首先检查 state 的值。如果 state 为 0,表示没有线程持有锁,则该线程可以立即获取读锁。如果 state 为 1,表示有线程持有读锁,则该线程会被加入到读队列中等待。如果 state 为 2,表示有线程持有写锁,则该线程会被加入到写队列中等待。

当一个线程试图获取写锁时,它会首先检查 state 的值。如果 state 为 0,表示没有线程持有锁,则该线程可以立即获取写锁。如果 state 为 1,表示有线程持有读锁,则该线程会被加入到写队列中等待。如果 state 为 2,表示有线程持有写锁,则该线程会被拒绝获取写锁。

当一个线程释放读锁时,它会检查 state 的值。如果 state 为 1,表示只有该线程持有读锁,则该线程会将 state 的值设置为 0,表示没有线程持有锁。如果 state 为 2,表示有其他线程持有读锁,则该线程会将 state 的值减 1,表示有更少的线程持有读锁。

当一个线程释放写锁时,它会检查 state 的值。如果 state 为 2,表示只有该线程持有写锁,则该线程会将 state 的值设置为 0,表示没有线程持有锁。如果 state 为 1,表示有其他线程持有读锁,则该线程会将 state 的值加 1,表示有更多的线程持有读锁。

使用场景

ReentrantReadWriteLock 适用于读操作远多于写操作的场景,例如:

  • 读写数据库:读写数据库时,通常读操作远多于写操作。使用 ReentrantReadWriteLock 可以允许多个线程同时进行读操作,从而提高并发性。
  • 缓存系统:缓存系统中,通常读操作远多于写操作。使用 ReentrantReadWriteLock 可以允许多个线程同时进行读操作,从而提高并发性。
  • 消息队列:消息队列中,通常读操作远多于写操作。使用 ReentrantReadWriteLock 可以允许多个线程同时进行读操作,从而提高并发性。

使用方法

ReentrantReadWriteLock 的使用相对简单,它提供了两个方法来获取锁:

  • readLock():获取读锁。如果当前有其他线程持有写锁,则该方法会阻塞当前线程,直到写锁被释放。
  • writeLock():获取写锁。如果当前有其他线程持有读锁或写锁,则该方法会阻塞当前线程,直到所有读锁和写锁都被释放。

ReentrantReadWriteLock 还提供了两个方法来释放锁:

  • unlockRead():释放读锁。
  • unlockWrite():释放写锁。

在使用 ReentrantReadWriteLock 时,需要注意以下几点:

  • 获取读锁和写锁时,必须使用 try-finally 语句,以确保锁在所有情况下都能被释放。
  • 在读锁和写锁的临界区内,不要进行耗时的操作,以免影响其他线程的性能。
  • 在读锁和写锁的临界区内,不要调用其他可能导致死锁的方法,例如:wait()notify()notifyAll()