写在开头

最近是和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. Java中一个逐渐被遗忘的强大功能,强到你难以置信!!

    大家好,我是冰河~~ 说起Java,简单好用,但是Java中很多牛逼的技术却逐渐被遗忘了~~ 在Java语言出现之前,很多系统都是使用C和C++开发的.Java出现之后,由于其面向对象的思想更加符合人 ...

  2. Scala 函数闭包和柯里化

    1 package com.atguigu.function 2 3 object HighFunction { 4 def main(args: Array[String]): Unit = { 5 ...

  3. ubuntu 联网

    使用NAT

  4. 5W1H聊开源之Who/When/Where——谁在何时何地“发明”了开源?

    美国政治传播学家拉斯韦尔提出了5W传播模式,经过后人的不断运用和发展总结,形成了一套逐渐成熟的"5W1H"体系,即:对选定的项目.工序或操作,都要从原因(何因Why).对象(何事W ...

  5. [IOI2000]邮局 题解

    简要题意 线段上有 \(V\) 个村庄,现在要建 \(P\) 个邮局,使每个村庄到最近的邮局的距离之和最小. 50分做法 设\(dp[i][j]\) 表示第一个村庄到第 \(i\) 个村庄,建了 \( ...

  6. Go 语言中结构体的使用和示例

    结构体(简称struct)用于创建不同数据类型的成员集合,放入一个单一的变量中.虽然数组用于将相同数据类型的多个值存储在单一变量中,但结构体用于将不同数据类型的多个值存储在单一变量中.结构体对于将数据 ...

  7. 初学STM32 SDIO(一)

    1. SDIO协议简介 ​ SDIO全称是安全数字输入/输出接口 ,控制器对SD卡进行读写通信操作一般有两种通信接口可选,一种是SPI接口,另外一种就是SDIO接口. 多媒体卡(MMC).SD卡. S ...

  8. 华为运动健康服务Health Kit 6.10.0版本新增功能速览!

    华为运动健康服务(HUAWEI Health Kit)6.10.0 版本新增的能力有哪些? 阅读本文寻找答案,一起加入运动健康服务生态大家庭! 一. 支持三方应用查询用户测量的连续血糖数据 符合申请H ...

  9. Win7 局域网服务器 - FTP 服务器搭建指南

    1. 打开 "开始" 菜单,找到控制面板 2. 选择 "程序" 3. 选择 "打开或关闭 Windows 功能" 4. 选择 "I ...

  10. CentOS 6.5 LAMP分主机平台的搭建及测试

    CentOS 6.5 LAMP分主机平台的搭建及测试 看似非常之完备,转来抽空测试学习 原文地址:http://www.it165.net/os/html/201403/7595.html 前言   ...