显式锁(三)读写锁ReadWriteLock
前言:
上一篇文章,已经很详细地介绍了 显式锁Lock 以及 其常用的实现方式- - ReetrantLock(重入锁),本文将介绍另一种显式锁 - - 读写锁ReadWriteLock。
前面介绍的隐式锁Synchronize、重入锁ReetrantLock都是互斥锁、独占锁,即同一个锁只能每时每刻至多由一个线程来获持有。互斥,是一种保守策略,虽然避免了“写/写”、“读/写”冲突,但也阻止了安全的“读/读”发生。然而,在不少情况下,数据结构上的操作都是“读操作”,如果此时能放宽加锁需求,允许多个读线程同时访问数据结构,那么将极大地提升程序的性能;而这种能支持共享的锁便被设计出来 - - 读写锁ReadWriteLock。
一、读写锁ReadWriteLock介绍
1、ReadWriteLock接口
首先,得明确一点,ReadWriteLock接口并没有继承Lock接口,可参考上一篇文章的继承结构图,ReadWriteLock 仅仅定义了两个方法,即readLock、writeLock方法;
Lock readLock( ): 返回用于读取操作的锁:
Lock writeLock( ): 返回用于写入操作的锁。
2、 读-写锁的性能
与互斥锁相比,读-写锁允许对共享数据进行更高级别的并发访问。虽然一次只有一个线程(writer 线程)可以修改共享数据,但在许多情况下,任何数量的线程可以同时读取共享数据(reader 线程),读-写锁利用了这一点。从理论上讲,与互斥锁相比,使用读-写锁所允许的并发性增强将带来更大的性能提高。但在实践中,只有在多处理器上频繁地访问读取数据结构,才能提高性能。 而在其他情况下,读-写锁的性能却比独占锁的性能要差一点,这是因为读-写锁的复杂性更高。所以要对程序进行分析,判断读-写锁是否能提高性能。
3、ReadWriteLock接口实现时的可选策略
尽管读-写锁的基本操作是直截了当的,但实现仍然必须作出许多决策,这些决策可能会影响给定应用程序中读-写锁的效果。这些策略的例子包括:
- 释放优先: 当一个写入操作释放写锁时,并且队列中同时存在读线程和写线程时,那么是读线程优先获得锁,还是写线程,或者说是最先发出请求的线程
- 读线程插队: 如果当读线程持有着读锁时,有写线程在等待,那么新到达的读线程能否立即获得访问权,还是应该在写线程后面等待?如果允许读线程插队到线程前面,那么将提高并发性,但却可能造成写线程发生饥饿问题。
- 重入性: 读锁、写锁是否允许重入。
- 降级: 如果一个线程持有写锁,那么它能否在不释放锁的情况下降级成一个读锁?
- 升级: 拥有读锁的线程能否优于其他正在等待的读线程和写线程而升级成为一个写锁?在大多数的读-写锁实现中并不支持升级,因为很容易造成死锁(如果两个读线程同时升级为写锁,那么二者都不会释放读取锁)
二、读写锁的实现类 - - ReentrantReadWriteLock
ReentrantReadWriteLock实现了ReadWriteLock接口。
1、ReentrantReadWriteLock的实现策略:
- 可重入的加锁;
- 提供公平锁与非公平锁(默认)的选择。与ReentrantLock类似,都是在构造方法中传入参数来决定,关于公平锁与非公平锁的详细可参考我的上一篇博文;
- 读线程不能插队: 尽管当读线程持有着读锁时,写线程等待获取锁,这时候新到达的其他读线程都必须等待它们前面的写线程使用完并释放了写锁,才能获得读锁。
- 写线程可以降级为读线程(支持降级),但是读线程不能升级为写线程(不支持升级);
2、ReentrantReadWriteLock 的读锁与写锁
ReentrantReadWriteLock实现的是ReadWriteLock接口,并没有实现Lock接口,但其管理的读锁ReentrantReadWriteLock.ReadLock 、写锁ReentrantReadWriteLock.WriteLock
都是其内部类,并且是实现Lock接口。注意以下两点:
- 读锁、写锁都支持定时获取锁、中断锁、非阻塞获取锁,与ReetrantLock相似;
- Condition 支持 :只能用于写锁,读锁是不支持的(因为读锁是共享锁)。写入锁提供了一个 Condition 实现,对于写入锁来说,该实现的行为与 ReentrantLock.newCondition() 提供的 Condition 实现对 ReentrantLock 所做的行为相同。
读取锁不支持 Condition,readLock().newCondition() 会抛出 UnsupportedOperationException;
3、ReentrantReadWriteLock 提供的监视系统状态的方法
boolean hasQueuedThread(Thread thread):
查询是否给定线程正在等待获取读取或写入锁。注意,因为随时可能发生取消操作,所以返回 true 并不保证此线程将获取锁。此方法主要用于监视系统状态。
boolean hasQueuedThreads( ):
查询是否所有的线程正在等待获取读取或写入锁。注意,因为随时可能发生取消操作,所以返回 true 并不保证任何其他线程将获取锁。此方法主要用于监视系统状态。
boolean hasWaiters(Condition condition)
查询是否有些线程正在等待与写入锁有关的给定条件。注意,因为随时可能发生取消操作,所以返回 true 并不保证任何其他线程将获取锁。此方法主要用于监视系统状态。
boolean isFair( )
如果此锁将公平性设置为 ture,则返回 true。
boolean isWriteLocked( )
查询是否某个线程保持了写入锁。
boolean isWriteLockedByCurrentThread( )
查询当前线程是否保持了写入锁。
int getWaitQueueLength(Condition condition)
返回正等待与写入锁相关的给定条件的线程估计数目。注意,因为随时可能发生超时和中断,所以只能将估计值作为实际等待线程数的上限。此方法设计用于监视系统状态,而不是同步控制。
int getWriteHoldCount( )
查询当前线程在此锁上保持的重入写入锁数量。
int getQueueLength( )
返回等待获取读取或写入锁的线程估计数目。
int getReadHoldCount( )
查询当前线程在此锁上保持的重入读取锁数量。
int getReadLockCount( )
查询为此锁保持的读取锁数量。
还有几个protected方法,不再详述。
@ Example1: 读写锁的使用实例
在使用某些种类的 Collection 时,可以使用 ReentrantReadWriteLock 来提高并发性。通常,在预期 collection 很大,读取者线程访问它的次数多于写入者线程,并且 entail 操作的开销高于同步开销时,这很值得一试。例如,以下是一个使用 TreeMap 的类,预期它很大,并且能被同时访问。
class RWDictionary {
private final Map<String, Data> m = new TreeMap<String, Data>();
private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
private final Lock r = rwl.readLock();
private final Lock w = rwl.writeLock();
public Data get(String key) {
r.lock();
try { return m.get(key); }
finally { r.unlock(); }
}
public String[] allKeys() {
r.lock();
try { return m.keySet().toArray(); }
finally { r.unlock(); }
}
public Data put(String key, Data value) {
w.lock();
try { return m.put(key, value); }
finally { w.unlock(); }
}
public void clear() {
w.lock();
try { m.clear(); }
finally { w.unlock(); }
}
}
@ Example2: ReentrantReadWriteLock的写锁降级
public static int count = 5;
public static void main(String[] args) {
//创建一个非公平的读写锁
ReadWriteLock lock = new ReentrantReadWriteLock(false);
Thread threadA = new Thread("threadA"){
@Override
public void run() {
//获取读锁
lock.readLock().lock();
System.out.println("成功获取读锁,count的值是:"+count);
if(count<10){
lock.readLock().unlock();
//在获取写锁前,必须先释放读锁
lock.writeLock().lock();
System.out.println("成功获取写锁");
count += count*3;
//获取读锁,此时没有释放写锁,即为写锁降级为读锁
lock.readLock().lock();
//成功获取读锁,写锁降级成功,释放写锁
lock.writeLock().unlock();
System.out.println("写锁成功降级成读锁,count的值是:"+count);
}
}
};
threadA.start();
}
运行结果:
成功获取读锁,count的值是:5
成功获取写锁
写锁成功降级成读锁,count的值是:20
显式锁(三)读写锁ReadWriteLock的更多相关文章
- Java并发-显式锁篇【可重入锁+读写锁】
作者:汤圆 个人博客:javalover.cc 前言 在前面并发的开篇,我们介绍过内置锁synchronized: 这节我们再介绍下显式锁Lock 显式锁包括:可重入锁ReentrantLock.读写 ...
- 【漫画】读写锁ReadWriteLock还是不够快?再试试StampedLock!
本文来源于公众号[胖滚猪学编程] 转载请注明出处! 在互斥锁ReentrantLock不好用?试试读写锁ReadWriteLock一文中,我们对比了互斥锁ReentrantLock和读写锁ReadWr ...
- 深刨显式锁ReentrantLock原理及其与内置锁的区别,以及读写锁ReentrantReadWriteLock使用场景
13.显示锁 在Java5.0之前,在协调对共享对象的访问时可以使用的机制只有synchronized和volatile.Java5.0增加了一种新的机制:ReentrantLock.与之前提到过的机 ...
- 【漫画】互斥锁ReentrantLock不好用?试试读写锁ReadWriteLock
ReentrantLock完美实现了互斥,完美解决了并发问题.但是却意外发现它对于读多写少的场景效率实在不行.此时ReentrantReadWriteLock来救场了!一种适用于读多写少场景的锁,可以 ...
- “全栈2019”Java多线程第三十二章:显式锁Lock等待唤醒机制详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
- “全栈2019”Java多线程第三十一章:中断正在等待显式锁的线程
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
- 读-写锁 ReadWriteLock & 线程八锁
读-写锁 ReadWriteLock: ①ReadWriteLock 维护了一对相关的锁,一个用于只读操作, 另一个用于写入操作. 只要没有 writer,读取锁可以由 多个 reader 线程同时保 ...
- C# 多线程编程之锁的使用【互斥锁(lock)和读写锁(ReadWriteLock)】
多线程编程之锁的使用[互斥锁(lock)和读写锁(ReadWriteLock)] http://blog.csdn.net/sqqyq/article/details/18651335 多线程程序写日 ...
- Java编程的逻辑 (71) - 显式锁
本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http: ...
随机推荐
- 原子性、可见性、synchronized 有好理解
原子性.可见性.synchronized 有好理解: from: https://blog.csdn.net/wohaqiyi/article/details/67635010 1.原子性 (1)原子 ...
- hasura graphql-engine v1.0.0-alpha25 的几个方便功能
hasura graphql-engine 是一个很不错的graphql 引擎,但是我们的数据模型经常可能会有变动, 但是以前的版本对于这些的处理,官方的方式是删除元数据,重启server,都不是很好 ...
- All the Apache Streaming Projects: An Exploratory Guide
The speed at which data is generated, consumed, processed, and analyzed is increasing at an unbeliev ...
- C#处理Exception的常用方法总结
在.NET中,异常是指成员没有完成它的名称宣称可以完成的行动.在异常的机制中,异常和某件事情的发生频率无关. 异常处理四要素包括:一个表示异常详细信息的类类型:一个向调用者引发异常类实例的成员:调用 ...
- WAJUEJI which home strong!nyoj
WAJUEJI which home strong! 时间限制:1000 ms | 内存限制:65535 KB 难度:2 描述 在一个山沟里,姐弟俩同时考上了大学.但由于家里拮据,所以这并不是 ...
- Javascript 在严格模式下不允许删除变量或对象
如下代码,运行后在浏览器中会报错. <script> "use strict"; var x = 3.14; delete x; </script>
- @Resource、@Autowired、@Qualifier 区别(表格显示)
@Resource.@Autowired.@Qualifier 区别(表格显示) 区别项 @Resource @Autowired @Qualifier 谁提供的 jdk提供,包是javax.anno ...
- centOS 6.5关闭防火墙步骤
centOS 6.5关闭防火墙步骤 关闭命令: service iptables stop 永久关闭防火墙:chkconfig iptables off 两个命令同时运行,运行完成后 ...
- win7 X64 使用VS2008 ->编译报错LINK : fatal error LNK1000: Internal error during Incr的解决
编译报错LINK : fatal error LNK1000: Internal error during Incr的解决 Win7 旗舰版 Microsoft Visual Studio 2008 ...
- HA 部署wordpress
前提: 1.保证免密认证ssh 2.NTP时间是否同步: 3.保证防火墙,selinux关闭: 4.用户名互相能够解析:在hosts文件设置: 环境: 系统:centos6.8和centos7.2 I ...