写在开头

最近是和java.util.concurrent.locks包下的同步类干上了,素有 并发根基 之称的concurrent包中全是精品,今天我们继续哈,今天学习的主题要由一个大厂常问的Java面试题开始:

小伙子,来说一说Java中的读写锁,你都用过哪些读写锁吧?

这个问题小伙伴们遇到了该如何回答呢?心里琢磨去吧,哈哈,不过build哥的回答要用从ReentrantReadWriteLock开始说起了,这个类也就是今天的主角,而它们同样是来自于java.util.concurrent.locks之下!

读写锁诞生的背景

在过去学习的过程中我们学过 synchronized、 ReentrantLock这种独占式锁,他们的好处是保证了线程的安全,缺点是同一时刻只能有一个线程持有锁,大大的影响了效率,而之前学过的Semaphore(信号量)这种呢,虽然支持同一时刻被多个线程获取,但它不能很好的保障线程安全性,我们需要的是一种效率高、安全性好的同步锁。

考虑到真正的生产生活中,对于数据的读取要比写入更为频繁,伟大的开发者们,将读数据的时候设置为共享锁,支持多个线程持有读锁,而在写的时候,考虑到线程安全,采用独占锁,同一时候仅允许一个线程持有写锁,在这种背景下读写锁应运而生!

读写锁:ReentrantReadWriteLock

ReentrantReadWriteLock是ReadWriteLock 接口的默认实现类,从名字可以看得出它也是一种具有可重入性的锁,同时也支持公平与非公平的配置,底层有两把锁,一把是 WriteLock (写锁),一把是 ReadLock(读锁) 。读锁是共享锁,写锁是独占锁。读锁可以被同时读,可以同时被多个线程持有,而写锁最多只能同时被一个线程持有,也是基于AQS实现的底层锁获取与释放逻辑。

内部构造

根据上面的构造图如果还没有搞清楚ReentrantReadWriteLock的底层构造的话,那我们跟入源码中取一探究竟吧!

【源码分析】

// 内部结构
private final ReentrantReadWriteLock.ReadLock readerLock;
private final ReentrantReadWriteLock.WriteLock writerLock;
final Sync sync;
/*1、用以继承AQS,获得AOS的特性,以及AQS的钩子函数*/
abstract static class Sync extends AbstractQueuedSynchronizer {
// 具体实现
}
/*非公平模式,默认为这种模式*/
static final class NonfairSync extends Sync {
// 具体实现
}
/*公平模式,通过构造方法参数设置*/
static final class FairSync extends Sync {
// 具体实现
}
/*读锁,底层是共享锁*/
public static class ReadLock implements Lock, java.io.Serializable {
private final Sync sync;
protected ReadLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
// 具体实现
}
/*写锁,底层是独占锁*/
public static class WriteLock implements Lock, java.io.Serializable {
private final Sync sync;
protected WriteLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
// 具体实现
} // 构造方法,初始化两个锁
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
} // 获取读锁和写锁的方法
public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; }

上面为底层的主要构造内容,ReentrantReadWriteLock中共写了5个静态内部类,各有功效,在上面的注释中也有提及。

使用案例

那么这个读写锁如何使用呢?我们写一个小小的测试案例,也感受一下。

【测试案例】

public class Test {
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private int data = 0; /**
* 写方法
* @param value
*/
public void write(int value) {
//注意,获取锁的操作要在try/finally外面
lock.writeLock().lock(); // 获取写锁
try {
data = value;
System.out.println("线程:"+Thread.currentThread().getName() + "写" + data);
} finally {
lock.writeLock().unlock(); // 释放写锁
}
} public void read() {
lock.readLock().lock(); // 获取读锁
try {
System.out.println("线程:" + Thread.currentThread().getName() + "读" + data);
} finally {
lock.readLock().unlock(); // 释放读锁
}
} public static void main(String[] args) {
Test test = new Test();
// 创建读线程
Thread readThread1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
test.read();
}
}); Thread readThread2 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
test.read();
}
}); // 创建写线程
Thread writeThread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
test.write(i);
}
}); readThread1.start();
readThread2.start();
writeThread.start(); try {
readThread1.join();
readThread2.join();
writeThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

输出:

线程:Thread-1读0
线程:Thread-0读0
线程:Thread-1读0
线程:Thread-2写0
线程:Thread-2写1
线程:Thread-2写2
线程:Thread-2写3
线程:Thread-0读3
线程:Thread-1读3
线程:Thread-2写4
线程:Thread-0读4
线程:Thread-1读4
线程:Thread-0读4
线程:Thread-1读4
线程:Thread-0读4

通过输出内容,我们进一步得证,在ReentrantReadWriteLock在使用读锁时,可以支持多个线程获取读资源,而在调用写锁时,其他读线程和写线程均阻塞等待当前线程写完。

存在的问题

虽然ReentrantReadWriteLock优化了原有的独占锁对于程序读写的性能,但它仍然存在一个弊端,就是 “写饥饿” ,因为在写的时候,是独占模式,其他线程不能读也不能写,这时候若有大量的读操作的话,那这些线程也只能等待着,从而带来写饥饿。

那这个问题怎么解决呢?我们在下一篇StampedLock(锁王)的讲解中,进行解答哈,敬请期待!

结尾彩蛋

如果本篇博客对您有一定的帮助,大家记得留言+点赞+收藏呀。原创不易,转载请联系Build哥!

如果您想与Build哥的关系更近一步,还可以关注“JavaBuild888”,在这里除了看到《Java成长计划》系列博文,还有提升工作效率的小笔记、读书心得、大厂面经、人生感悟等等,欢迎您的加入!

Java中的读写锁ReentrantReadWriteLock详解,存在一个小缺陷的更多相关文章

  1. java 可重入读写锁 ReentrantReadWriteLock 详解

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt206 读写锁 ReadWriteLock读写锁维护了一对相关的锁,一个用于只 ...

  2. Java中的多线程技术全面详解

    本文主要从整体上介绍Java中的多线程技术,对于一些重要的基础概念会进行相对详细的介绍,若有叙述不清晰或是不正确的地方,希望大家指出,谢谢大家:) 为什么使用多线程 并发与并行 我们知道,在单核机器上 ...

  3. Java 中的异常和处理详解

    Java 中的异常和处理详解 原文出处: 代码钢琴家 简介 程序运行时,发生的不被期望的事件,它阻止了程序按照程序员的预期正常执行,这就是异常.异常发生时,是任程序自生自灭,立刻退出终止,还是输出错误 ...

  4. Java中的读写锁

    一.读写锁 1.初识读写锁 a)Java中的锁——Lock和synchronized中介绍的ReentrantLock和synchronized基本上都是排它锁,意味着这些锁在同一时刻只允许一个线程进 ...

  5. java中的读/写锁

    读写锁接口:ReadWriteLock,它的具体实现类为:ReentrantReadWriteLock 使用场景: 对于一个资源,读读能共存,读写不能共存,写写不能共存. 锁降级:从写锁变成读锁: 锁 ...

  6. java并发之读写锁ReentrantReadWriteLock的使用

    Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象.两个线程执行的代码片段要实现同步互斥的效果,它们必须用同一个Lock对象. 读写锁:分为读 ...

  7. 22、Java并发性和多线程-Java中的读/写锁

    以下内容转自http://ifeve.com/read-write-locks/: 相比Java中的锁(Locks in Java)里Lock实现,读写锁更复杂一些.假设你的程序中涉及到对一些共享资源 ...

  8. Java中的IO流系统详解(转载)

    摘要: Java 流在处理上分为字符流和字节流.字符流处理的单元为 2 个字节的 Unicode 字符,分别操作字符.字符数组或字符串,而字节流处理单元为 1 个字节,操作字节和字节数组. Java ...

  9. Java中的IO流系统详解

    Java 流在处理上分为字符流和字节流.字符流处理的单元为 2 个字节的 Unicode 字符,分别操作字符.字符数组或字符串,而字节流处理单元为 1 个字节,操作字节和字节数组. Java 内用 U ...

  10. JAVA中IO和NIO的详解分析,内容来自网络和自己总结

    用一个例子来阐释: 一辆客车上有10个乘客,他们的目的地各不相同,当没有售票员的时候,司机就需要不断的询问每一站是否有乘客需要下车,需要则停下,不需要则继续开车,这种就是阻塞的方式. 当有售票员的时候 ...

随机推荐

  1. 创建远程仓库&克隆项目(Github)

    创建远程仓库 在GitHub上注册一个账号,之后creat a new repository 创建的远程仓库把它看作一个百度网盘就可以了 克隆项目 1.远程仓库可以下载\克隆到本地 code :git ...

  2. table边框改为细线

    默认的TABLE边线设置即使是1px 是很粗的,会使用其他一些方法来制作出细线边框. 利用CSS来实现细线的方法,很有效,而且兼容所有浏览器. 细线表格如果单纯设置边框,很难保证浏览器兼容.常见的做法 ...

  3. .net 开源混淆器 ConfuserEx

    官网:http://yck1509.github.io/ConfuserEx/ 下载地址:https://github.com/yck1509/ConfuserEx/releases 使用参考:htt ...

  4. C++ future

    promise 空模板 非 void 特化,用于在线程间交流对象 void 特化,用于交流无状态事件 类模板 std::promise 提供存储值或异常的设施,之后通过 std::promise 对象 ...

  5. SC的板子库~

    观前须知 Sugar_Cube的博客园主页 声明 本文所有内容遵循CC BY-NC-SA 4.0 Deed原则 本文包含了笔者常用的OI算法.数据结构的模板 不保证算法最优,但能通过相应的模板题(如果 ...

  6. 网络协议之:haproxy的Proxy Protocol代理协议

    目录 简介 Proxy Protocol的实现细节 版本1 版本2 Proxy Protocol的使用情况 总结 简介 代理大家应该都很熟悉了,比较出名的像是nginx,apache HTTPD,st ...

  7. 响应式系统reactive system初探

    目录 初识响应式系统 什么是响应式系统 响应式系统的四大特点 及时响应性(Responsive) 恢复性(Resilient) 有弹性(Elastic) 消息驱动(Message Driven) 总结 ...

  8. Matplotlib绘图设置---坐标轴刻度和标签设置

    每个axes对象都有xaxis和yaxis属性,且xaxis和yaxis的每一个坐标轴都有主要刻度线/标签和次要刻度线/标签组成,标签位置通过一个Locator对象设置,标签格式通过一个Formatt ...

  9. Solr安全控制,开启基本身份验证

    前言:请各大网友尊重本人原创知识分享,谨记本人博客:南国以南i 背景:solr默认安装没带权限控制,每次进入页面直接操作都有点裸奔的感觉. 一.SolrCloud集群模式 说明:要使用基本身份验证您必 ...

  10. C# Replace方法

    例子: string tStw = "Run Status"; string tStw1 = tStw.Replace("Run Status", " ...