ReentrantLock保证了只有一个线程可以执行临界区代码。但是有些时候,这种保护有点过头。因为我们发现,任何时刻,只允许一个线程修改,也就是调用inc()方法是必须获取锁,但是,get()方法只读取数据,不修改数据,它实际上允许多个线程同时调用。

实际上我们想要的是:允许多个线程同时读,但只要有一个线程在写,其他线程就必须等待。

使用ReadWriteLock可以解决这个问题,它保证:

  • 只允许一个线程写入(其他线程既不能写入也不能读取);
  • 没有写入时,多个线程允许同时读(提高性能)。

    ReadWriteLock

  • 读锁不支持条件对象,写锁支持条件对象

  • 读锁不能升级为写锁,写锁可以降级为读锁
  • 读写锁也有公平和非公平模式
  • 读锁支持多个读线程进入临界区,写锁是互斥的

    ReadWriteLock维护一对相关联的锁,一个用于只读操作,一个用于写操作。读锁可以被多个读线程同时持有,只要没有写线程。写锁是排他的。

所有的ReadWriteLock实现必须保证,writeLock操作的内存同步效果(在Lock接口中指定)也与相关的readLock保持一致。也就是说,成功获取读锁的线程将看到在之前释放写锁时所做的所有更新。

在访问共享数据时,读写锁比互斥锁允许的并发级别更高。它利用了这样一个事实:虽然一次只有一个线程(写入线程)可以修改共享数据,但在许多情况下,任意数量的线程都可以并发地读取数据(因此是读取线程)。从理论上讲,与使用互斥锁相比,使用读写锁所允许的并发性的增加将导致性能的改进。在实践中,这种并发性的增加只能在多处理器上完全实现,而且只有在共享数据的访问模式合适的情况下才能实现。

读写锁是否会提高性能的使用互斥锁的频率取决于读取数据被修改相比,读和写操作的持续时间,和争用数据——也就是说,线程的数量,将尝试读或写数据在同一时间。例如,最初填充数据,之后不经常修改,但经常搜索(比如某种目录)的集合是使用读写锁的理想候选对象。然而,如果更新变得频繁,那么数据的大部分时间都被独占锁定,并发性增加的可能性很小。此外,如果读操作太短,读写锁实现的开销(它本质上比互斥锁更复杂)可能会控制执行成本,特别是许多读写锁实现仍然通过一小段代码序列化所有线程。最终,只有分析和度量才能确定读写锁的使用是否适合您的应用程序。

尽管读写锁的基本操作很简单,但实现必须做出许多策略决策,这些决策可能会影响给定应用程序中读写锁的有效性。这些政策的例子包括:

锁公平性(读锁和写锁都在等待时,授予读锁还是写锁?)

在读锁处于活动状态且写锁处于等待状态时,决定是否授予请求读锁的reader以读锁。优先选择reader会无限期地延迟writer,而优先选择writer会降低并发性的可能性。

确定锁是否可重入:具有写锁的线程能否重新获取它?它能在保持写锁的同时获得读锁吗?读锁本身是可重入的吗?
可以将写锁降级为读锁而不允许写入者介入吗?是否可以将读锁升级为写锁,而不是其他等待的读锁或写锁?

ReentrantReadWriteLock

ReadWriteLock的实现,支持类似于ReentrantLock的语义。
这个类有以下属性:

Acquisition order

该类不会为锁访问强加读取器或写入器首选项顺序。然而,它确实支持可选的公平策略。

非公平模式(默认)

当构造为非公平(默认)时,读和写锁的进入顺序是不指定的,受重入约束。持续竞争的非公平锁可能会无限期地延迟一个或多个读线程或写线程,但通常会比公平锁具有更高的吞吐量。

公平的方式

当构造为公平时,线程使用近似到达顺序策略竞争条目。当当前持有的锁被释放时,要么给等待时间最长的单个写线程分配写锁,要么给一组等待时间长于所有等待写线程的读线程分配读锁。
如果一个线程试图获得一个公平读锁(不可重入),如果写锁被持有,或者有一个写线程正在等待,那么这个线程将会阻塞。在当前最老的正在等待的写线程获得并释放写锁之后,线程才会获得读锁。当然,如果正在等待的写线程放弃了等待,留下一个或多个读线程作为队列中最长的等待者,并且没有写锁,那么这些读线程将被分配读锁。
试图获取公平写锁(非重入)的线程将会阻塞,除非读锁和写锁都是空闲的(这意味着没有等待的线程)。(注意,非阻塞的reentrtrantreadwritelock . readlock . trylock()和reentrtrantreadwritelock . writelock . trylock()方法不遵守这个公平设置,并且会在可能的情况下立即获取锁,不管等待的线程是什么。)

可重入性

读锁和写锁都可以重入。但写锁释放前不能重入读锁。
writer can acquire the read lock.
reader can not acquire the write lock.
在其他应用程序中,当在调用或回调在读锁下执行读操作的方法时持有写锁时,可重入性可能很有用。如果读取器试图获取写锁,它将永远不会成功。

锁降级

可重入性还允许将写锁降级为读锁,方法是先获取写锁,然后获取读锁,然后释放写锁。但是,从读锁升级到写锁是不可能的。

锁定获取中断

读锁和写锁都支持在锁获取期间中断。

条件支持

就写锁而言,写锁提供的条件实现与ReentrantLock提供的条件实现的行为方式相同。对于ReentrantLock,则使用newCondition。当然,这个条件只能与写锁一起使用。
读锁不支持条件,readLock(). newcondition()抛出UnsupportedOperationException。

仪表

该类支持确定锁是被持有还是争用的方法。这些方法是为了监视系统状态而设计的,而不是为了同步控制。

该类的序列化行为与内置锁的行为相同:反序列化的锁处于解锁状态,而与序列化时的状态无关。

读锁和写锁的状态表示

image.png