前言
相关系列
- 《Java ~ Lock【目录】》(持续更新)
- 《Java ~ Lock ~ ReadWriteLock【源码】》(学习过程/多有漏误/仅作参考/不再更新)
- 《Java ~ Lock ~ ReadWriteLock【总结】》(学习总结/最新最准/持续更新)
- 《Java ~ Lock ~ ReadWriteLock【问题】》(学习解答/持续更新)
涉及内容
- 《Java ~ Lock【总结】》
- 《Java ~ Lock ~ ReentrantReadWriteLock【总结】》
一 ReadWriteLock(读写锁)接口源码及机制详解
接口
ReadWriteLock(读写锁)接口在API层面定义了读写锁的概念。读写锁是在独占锁的基础上衍生/细化出来的概念,其主张将执行读/写操作的线程进行区别对待。由于单纯的读操作并发并不会导致共享数据出现线程安全问题,因此读写锁允许读线程在无写线程时随意共享/并发访问共享数据以提升程序性能,而只在写线程访问时独占共享数据以保证线程安全。读写锁内部维护了一对关联的读/写锁,分别交由执行读/写操作的线程持有。读锁是共享特性的锁,已持有读锁的读线程并不会妨碍其它读线程试图持有读锁及访问共享数据。而写锁则是独占特性的锁,已持有写锁的写线程会独占访问共享数据并阻止其它所有读/写线程对读/写的持有,从而满足读写锁“读读并发/读写互斥/写写互斥”的概念设计。
读写锁仅在多核处理器中提升性能。读写锁相对于独占锁的优势在于其解放了读操作的并发性能,而由于单核处理器的并发本质是串行分片执行,因此读写锁在单核处理器中相比独占锁并不能取得良好的性能提升。
读写锁的性能提升效果会受到多方因素的影响。即使是在多核处理器下读写锁的性能提升效果也受到多方因素的影响,这主要取决于读/写操作在执行频率/时长两个方面的占比。在执行频率方面,我们很容易就能推测出读写锁可以在读操作频率占比越高的场景带来更大性能提升,因此这意味着读线程可以在大部分时间里并发访问共享数据。而在执行时长方面读操作的时长越短则越有利于读写锁的性能提升,因为这意味着读写锁在相同时间内可以比独占锁支持更多数量的读操作执行。除此以外读写锁的性能提升效果还受到线程竞争的影响,因为读/写线程对读/写锁的竞争必然也会对性能产生一定的负面效果。总之,读写锁是否适用于我们希望使用的场景最终还是要通过资料收集和数据统计来确定。
尽管读写锁接口定义的基础操作相当直截了当,但开发者在设计读写锁接口的实现类时依然有一些需要注意的点,它可能会影响读写锁在程序中的有效性。这些点包含但不限于:
- 更偏向于令读/写线程中的哪个持有读/写锁?偏向读线程可能导致写线程无限延迟;而偏向写线程可能导致性能提升不明显。
- 在读操作执行频率/时长较高/长时如何处理写线程的长时间无法持有写锁的情况?
- 是否令读/写线程公平的获取读/写锁?
- 读/写锁能否重入?
- 线程能否同时获得读/写锁?
- 写锁能否降级为读锁?本质是独占锁能否降级为共享锁。
- 读锁能否升级为写锁?本质是共享锁能否升级为独占锁。
/**
* A {@code ReadWriteLock} maintains a pair of associated {@link Lock locks}, one for read-only operations and one
* for writing. The {@link #readLock read lock} may be held simultaneously by multiple reader threads, so long as
* there are no writers. The {@link #writeLock write lock} is exclusive.
* 一个读写锁维护一对关联的锁,一个只用于读取操作,一个只用于写入操作。读锁可能同时被多个读取线程持有,只要
* 没有写入者就行。写锁是排它的。
*
* <p>
* All {@code ReadWriteLock} implementations must guarantee that the memory synchronization effects of
* {@code writeLock} operations (as specified in the {@link Lock} interface) also hold with respect to the associated
* {@code readLock}. That is, a thread successfully acquiring the read lock will see all updates made upon previous
* release of the write lock.
* 所有读写锁实现必须保证写操作的同步作用也涉及持有关联的读锁[即持有写锁时无法持有读锁...但如果是一个线程同时
* 持有两种锁的情况需要额外处理]。一个线程持有读锁后将看见过先前写锁释放后的所有更新。
* <p>
* A read-write lock allows for a greater level of concurrency in accessing shared data than that permitted by a
* mutual exclusion lock. It exploits the fact that while only a single thread at a time (a <em>writer</em> thread) can
* modify the shared data, in many cases any number of threads can concurrently read the data (hence <em>reader</em>
* threads). In theory, the increase in concurrency permitted by the use of a read-write lock will lead to performance
* improvements over the use of a mutual exclusion lock. In practice this increase in concurrency will only be fully
* realized on a multi-processor, and then only if the access patterns for the shared data are suitable.
* 一个读写锁允许比互斥锁更高等级的并发访问共享数据。它运用在一个时间只有一天线程可以修改数据的现实,在某些
* 情况下令人任意数量的线程读取共享数据。在理论上,与使用互斥锁相比,使用读写锁所允许的并发增长将导致性能更
* 高。在实际中该并发增长只在多处理器且仅访问适配模板的共享数据时充分实现[单处理器中读写锁确实没有什么意义,
* 因为本质都是串行执行]
* <p>
* Whether or not a read-write lock will improve performance over the use of a mutual exclusion lock depends on the
* frequency that the data is read compared to being modified, the duration of the read and write operations, and the
* contention for the data - that is, the number of threads that will try to read or write the data at the same time.
* For example, a collection that is initially populated with data and thereafter infrequently modified, while being
* frequently searched (such as a directory of some kind) is an ideal candidate for the use of a read-write lock.
* However, if updates become frequent then the data spends most of its time being exclusively locked and there is
* little, if any increase in concurrency. Further, if the read operations are too short the overhead of the read-write
* lock implementation (which is inherently more complex than a mutual exclusion lock) can dominate the execution
* cost, particularly as many read-write lock implementations still serialize all threads through a small section of code.
* Ultimately, only profiling and measurement will establish whether the use of a read-write lock is suitable for your
* application.
* 一个读写锁能否改善性能超过使用互斥锁取决于数据读取相较于修改的频率,读取/写入的时间和写入操作,以及数据
* 竞争 - 即多个线程在同一时间尝试读取/写入数据[更大的并发意味着更大的竞争,更大的竞争意味着更大的性能损耗]。
* 例如:一个始被数据充满并且后续被不频繁/频繁的修改/查询的集将是使用读写锁的理想候选。此外,如果更新变得频
* 繁那么数据大部分时间将被独占并且并发几乎没有增长。进一步说,如果读操作很短那么读写锁实现(通常比互斥锁更
* 复杂)的开销可以在执行花费中占绝对优势,特别是许多读写锁实现依然通过一个代码段来序列化[串行]所有线程。最
* 终,只有资料收集和数据统计将确定读写锁是否适用于你的程序。
* <p>
* Although the basic operation of a read-write lock is straight-forward, there are many policy decisions that an
* implementation must make, which may affect the effectiveness of the read-write lock in a given application.
* Examples of these policies include:
* 尽管读写锁的基础操作是直截了当的,但一些策略决策必须注意,它可能影响读写锁在指定的程序中的有效性。这些策
* 略的案例包括:
* <ul>
* <li>Determining whether to grant the read lock or the write lock, when both readers and writers are waiting, at the
* time that a writer releases the write lock. Writer preference is common, as writes are expected to be short and
* infrequent. Reader preference is less common as it can lead to lengthy delays for a write if the readers are frequent
* and long-lived as expected. Fair, or "in-order" implementations are also possible.
* 当有写线程释放写锁且有读线程与写线程都等待时确定是否授予读锁或写锁。写入者性能一般,因此写入期望短暂并且
* 不频繁。读取者性能低于正常的情况下如果读取者如预期般频繁且长时间存活,则它会导致写入漫长的延迟。公平,或
* 者按顺序实现也可能。
* <li>Determining whether readers that request the read lock while a reader is active and a writer is waiting, are
* granted the read lock. Preference to the reader can delay the writer indefinitely, while preference to the writer can
* reduce the potential for concurrency.
* 当一个读取者可用以及一个写入者正在等待时,确定读取者请求读锁时是否授予读锁。偏向读取者可能令写入者无限延
* 迟,偏向写入者可能减少并发的潜力。
* <li>Determining whether the locks are reentrant: can a thread with the write lock reacquire it? Can it acquire a read
* lock while holding the write lock? Is the read lock itself reentrant?
* 确定锁是否可重入:线程能否伴随写锁获取它?在持有写锁期间能否持有读锁?是否读取锁自身的重入?
* <li>Can the write lock be downgraded to a read lock without allowing an intervening writer? Can a read lock be
* upgraded to a write lock, in preference to other waiting readers or writers?
* 没有允许一个干扰写入者的情况下写锁能否降级为读锁?一个读锁升级为一个写锁,更偏向于等待中的读取者还是写入
* 者?
* </ul>
* You should consider all of these things when evaluating the suitability of a given implementation for your application.
* 当评估你程序中的指定实现是你应该考虑上述的所有情况。
*
* @author Doug Lea
* @see ReentrantReadWriteLock
* @see Lock
* @see ReentrantLock
* @since 1.5
*/
public interface ReadWriteLock {
...
}
方法
- Lock readLock() —— 读锁 —— 获取当前读写锁的读锁。
/**
* Returns the lock used for reading.
* 返回用于读取的锁
*
* @return the lock used for reading 读锁
* @Description: --------------------------------------------------------- 名称 ---------------------------------------------------------
* ---- 读锁
* @Description: --------------------------------------------------------- 作用 ---------------------------------------------------------
* ---- 获取当前读写锁的读锁
* @Description: --------------------------------------------------------- 注意 ---------------------------------------------------------
*/
Lock readLock();
- Lock writeLock() —— 写锁 —— 获取当前读写锁的写锁。
/**
* Returns the lock used for writing.
* 返回用于写入的锁
*
* @return the lock used for writing 写锁
* @Description: --------------------------------------------------------- 名称 ---------------------------------------------------------
* ---- 写锁
* @Description: --------------------------------------------------------- 作用 ---------------------------------------------------------
* ---- 获取当前读写锁的写锁
* @Description: --------------------------------------------------------- 注意 ---------------------------------------------------------
*/
Lock writeLock();