ReentrantReadWriteLock底层是基于ReentrantLock和AbstractQueuedSynchronizer来实现的,所以,ReentrantReadWriteLock的数据结构也依托于AQS的数据结构。
public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {}
ReentrantReadWriteLock有五个内部类,五个内部类之间也是相互关联的。内部类的关系如下图所示。
说明: 如上图所示,Sync继承自AQS、NonfairSync继承自Sync类、FairSync继承自Sync类;ReadLock实现了Lock接口、WriteLock也实现了Lock接口。
- public ReentrantReadWriteLock() {
- this(false);
- }
说明: 此构造函数会调用另外一个有参构造函数。
- public ReentrantReadWriteLock(boolean fair) {
- // 公平策略或者是非公平策略
- sync = fair ? new FairSync() : new NonfairSync();
- // 读锁
- readerLock = new ReadLock(this);
- // 写锁
- writerLock = new WriteLock(this);
- }
说明: 可以指定设置公平策略或者非公平策略,并且该构造函数中生成了读锁与写锁两个对象。
下面给出了一个使用ReentrantReadWriteLock的示例,源代码如下。
- import java.util.concurrent.locks.ReentrantReadWriteLock;
-
- class ReadThread extends Thread {
- private ReentrantReadWriteLock rrwLock;
-
- public ReadThread(String name, ReentrantReadWriteLock rrwLock) {
- super(name);
- this.rrwLock = rrwLock;
- }
-
- public void run() {
- System.out.println(Thread.currentThread().getName() + " trying to lock");
- try {
- rrwLock.readLock().lock();
- System.out.println(Thread.currentThread().getName() + " lock successfully");
- Thread.sleep(5000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- } finally {
- rrwLock.readLock().unlock();
- System.out.println(Thread.currentThread().getName() + " unlock successfully");
- }
- }
- }
-
- class WriteThread extends Thread {
- private ReentrantReadWriteLock rrwLock;
-
- public WriteThread(String name, ReentrantReadWriteLock rrwLock) {
- super(name);
- this.rrwLock = rrwLock;
- }
-
- public void run() {
- System.out.println(Thread.currentThread().getName() + " trying to lock");
- try {
- rrwLock.writeLock().lock();
- System.out.println(Thread.currentThread().getName() + " lock successfully");
- } finally {
- rrwLock.writeLock().unlock();
- System.out.println(Thread.currentThread().getName() + " unlock successfully");
- }
- }
- }
-
- public class ReentrantReadWriteLockDemo {
- public static void main(String[] args) {
- ReentrantReadWriteLock rrwLock = new ReentrantReadWriteLock();
- ReadThread rt1 = new ReadThread("rt1", rrwLock);
- ReadThread rt2 = new ReadThread("rt2", rrwLock);
- WriteThread wt1 = new WriteThread("wt1", rrwLock);
- rt1.start();
- rt2.start();
- wt1.start();
- }
- }
运行结果(某一次):
- rt1 trying to lock
- rt2 trying to lock
- wt1 trying to lock
- rt1 lock successfully
- rt2 lock successfully
- rt1 unlock successfully
- rt2 unlock successfully
- wt1 lock successfully
- wt1 unlock successfully
(api方法较为简单,这里不做过多分析。总而言之,就是可共享读,不可共享写,读写互斥读读共享)
接下来看一个锁降级的示例。因为数据不常变化,所以多个线程可以并发地进行数据处理,当数据变更后,如果当前线程感知到数据变化,则进行数据的准备工作,同时其他处理线程被阻塞,直到当前线程完成数据的准备工作,如代码如下所示:
- public void processData() {
- readLock.lock();
- if (!update) {
- // 必须先释放读锁
- readLock.unlock();
- // 锁降级从写锁获取到开始
- writeLock.lock();
- try {
- if (!update) {
- // 准备数据的流程(略)
- update = true;
- }
- readLock.lock();
- } finally {
- writeLock.unlock();
- }
- // 锁降级完成,写锁降级为读锁
- }
- try {
- // 使用数据的流程(略)
- } finally {
- readLock.unlock();
- }
- }
锁降级中读锁的获取是否必要呢? 答案是必要的。主要是为了保证数据的可见性,如果当前线程不获取读锁而是直接释放写锁,假设此刻另一个线程(记作线程T)获取了写锁并修改了数据,那么当前线程无法感知线程T的数据更新。如果当前线程获取读锁,即遵循锁降级的步骤,则线程T将会被阻塞,直到当前线程使用数据并释放读锁之后,线程T才能获取写锁进行数据更新。