前言:

  上一篇文章,已经很详细地介绍了 显式锁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的更多相关文章

  1. Java并发-显式锁篇【可重入锁+读写锁】

    作者:汤圆 个人博客:javalover.cc 前言 在前面并发的开篇,我们介绍过内置锁synchronized: 这节我们再介绍下显式锁Lock 显式锁包括:可重入锁ReentrantLock.读写 ...

  2. 【漫画】读写锁ReadWriteLock还是不够快?再试试StampedLock!

    本文来源于公众号[胖滚猪学编程] 转载请注明出处! 在互斥锁ReentrantLock不好用?试试读写锁ReadWriteLock一文中,我们对比了互斥锁ReentrantLock和读写锁ReadWr ...

  3. 深刨显式锁ReentrantLock原理及其与内置锁的区别,以及读写锁ReentrantReadWriteLock使用场景

    13.显示锁 在Java5.0之前,在协调对共享对象的访问时可以使用的机制只有synchronized和volatile.Java5.0增加了一种新的机制:ReentrantLock.与之前提到过的机 ...

  4. 【漫画】互斥锁ReentrantLock不好用?试试读写锁ReadWriteLock

    ReentrantLock完美实现了互斥,完美解决了并发问题.但是却意外发现它对于读多写少的场景效率实在不行.此时ReentrantReadWriteLock来救场了!一种适用于读多写少场景的锁,可以 ...

  5. “全栈2019”Java多线程第三十二章:显式锁Lock等待唤醒机制详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  6. “全栈2019”Java多线程第三十一章:中断正在等待显式锁的线程

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  7. 读-写锁 ReadWriteLock & 线程八锁

    读-写锁 ReadWriteLock: ①ReadWriteLock 维护了一对相关的锁,一个用于只读操作, 另一个用于写入操作. 只要没有 writer,读取锁可以由 多个 reader 线程同时保 ...

  8. C# 多线程编程之锁的使用【互斥锁(lock)和读写锁(ReadWriteLock)】

    多线程编程之锁的使用[互斥锁(lock)和读写锁(ReadWriteLock)] http://blog.csdn.net/sqqyq/article/details/18651335 多线程程序写日 ...

  9. Java编程的逻辑 (71) - 显式锁

    ​本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http: ...

随机推荐

  1. jboss 5.1 启动问题解决

    在安装好后启动时可能遇到这样的情况: ERROR [AbstractKernelController] Error installing to Instantiated: name=Attachmen ...

  2. Java-如何不使用-volatile-和锁实现共享变量的同步操作

    from: http://thinkinjava.cn/2018/06/Java-%E5%A6%82%E4%BD%95%E4%B8%8D%E4%BD%BF%E7%94%A8-volatile-%E5% ...

  3. synchronized (lock) 买票demo 线程安全

    加锁防止多个线程执行同一段代码! /** http://blog.51cto.com/wyait/1916898 * @author * @since 11/10/2018 * 某电影院目前正在上映贺 ...

  4. spring boot 好文

    配置: https://www.jianshu.com/p/3af2a8721d86 : Spring Boot启动报错:Whitelabel Error Page 分页: https://bbs.c ...

  5. nyoj 吃土豆

    吃土豆 时间限制:1000 ms  |  内存限制:65535 KB 难度:4   描述 Bean-eating is an interesting game, everyone owns an M* ...

  6. ActiveMQ默认协议和IO模型优化

    在ActiveMQ的官方网站上,列出了目前ActiveMQ中支持的所有消息协议,它们是:AMQP.MQTT.OpenWire.REST.Stomp.XMPP: 不同的协议需要设置不同的网络监听端口,这 ...

  7. chrome 小技巧:保持元素的hover状态

    审查元素,选中需要hover的标签 点击"Styles"菜单中的":hov",弹出 Force element state 选中相应的 :hover :acti ...

  8. Microsoft Dynamics CRM 如何修改域密码

    一.安装IIS6脚本工具,如下图所示: 二.复制iisadmpwd文件夹到AD Server的C:\Windows\SysWOW64\inetsrv文件夹下 三.注册Iisadmpwd目录下的IISp ...

  9. CC2530中串口波特率改为9600时单个数据包来不及接收的解决方案

    在调试CC2530过程中发现波特率改为9600时,单个包仅有3个Byte时,接收DMA就会启动 因而数据包被强迫拆分成多个,显然只要将接收DMA启动延时做到足够大即可. 具体修改内容如下图所示: 经过 ...

  10. Linux操作系统中/sbin/init程序的执行过程

    当init启动后,它通过执行各种启动事务来继续引导进程(检查并监视文件系统,启动后台程序daemons,等等),直至完成用户所有操作环境的设置工作.这里主要涉及4个程序:init.getty(aget ...