ReentrantReadWriteLock读写锁简单原理案例证明
ReentrantReadWriteLock存在原因?
我们知道List的实现类ArrayList,LinkedList都是非线程安全的,Vector类通过用synchronized修饰方法保证了List的多线程非安全问题,但是有个缺点:读写同步,效率低下。于是就出现了CopyOnWriteArrayList,它通过写时复制数组实现了读写分离,提高了多线程对List读的效率,适合多读少些的情况。同理:我们知道ReentrantLock,它是一把独占的锁,是用来控制线程同步的,如果我们用ReentrantLock来实现ArrayList安全,能否达到CopyOnWriteArrayList同样的效果呢?
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @author :jiaolian
* @date :Created in 2021-01-26 15:49
* @description:ReentrantReadWriteLock多读少写的场景
* @modified By:
* 公众号:叫练
*/
public class MultReadTest {
private static class MyList {
private final ReentrantReadWriteLock REENTRANT_READ_WRITE_LOCK = new ReentrantReadWriteLock();
private final ReentrantReadWriteLock.WriteLock WRITE_LOCK = REENTRANT_READ_WRITE_LOCK.writeLock();
private final ReentrantReadWriteLock.ReadLock READ_LOCK = REENTRANT_READ_WRITE_LOCK.readLock();
private List<String> list = new ArrayList();
//读list
public void readList() {
try {
READ_LOCK.lock();
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+":"+list.size());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
READ_LOCK.unlock();
}
}
//写list
public void writeList() {
try {
WRITE_LOCK.lock();
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+":新增1个元素");
list.add("叫练【公众号】");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
WRITE_LOCK.unlock();
}
}
}
public static void main(String[] args) {
MyList myList = new MyList();
//读写锁适合多读少写情况
//新建10个读线程,1个写线程
new Thread(()->{myList.writeList();},"写线程").start();
for (int i=0; i<10; i++) {
new Thread(()->{myList.readList();},"读线程"+(i+1)).start();
}
}
}
上面案例我们用ReentrantReadWriteLock实现了CopyOnWriteArrayList,主线程新建了1个写线程写list,10个读线程读list,程序一共花费2执行完毕,如果用Vector需要花费11秒。在多线程的情况下,通过读写锁操作List,提高了List的读效率,在List读的部分,线程是共享的,在对List写的过程中,在对写的线程是同步的,因此我们可以得出一个结论:读写锁是读读共享,读写同步!
独占获取锁简单流程

如上图,我们简单的梳理下独占获取锁流程。
- 独占锁获取(上述例子中的WRITE_LOCK写锁),首先判断是否有线程获取了锁,是否有线程获取了锁的判断通过读写锁中通过32位int类型state可以获取,其中低16位表示读锁,高16表示写锁。
- 有读锁:直接排队阻塞。
- 有写锁:还需要判断写锁线程是否是自己,如果是自己就是锁重入了,如果不是自己说明已经有其他的线程获取锁正在执行,那么当前线程需要排队阻塞。
- 无锁:直接获取锁,其他抢占的独占锁线程需要排队阻塞,当前线程执行完毕后释放锁通知下一个排队线程获取锁。
共享获取锁简单流程

如上图,我们简单的梳理下共享锁获取锁流程。
- 独占锁获取(上述例子中的READ_LOCK读锁),首先判断是否有线程获取了锁。
- 有读锁:当前线程发现此时读锁状态被占用,说明有线程获取了读锁。该线程通过cas自旋【死循环】获取到读锁为止。
- 有写锁:还需要判断持有写锁的线程是否是自己,如果是自己而且此时是获取的是读锁会获取锁成功,我们称为锁降级,如果不是自己说明此时有其他线程获取了写锁,那么当前线程需要排队阻塞。
- 无锁:直接获取锁。
写锁降级
我们说读写互斥,但同一个线程中,先写后读也是允许的,我们称之为锁降级。在面试中共享锁面试频率也比较高,方便理解我们举个简单的案例说明下。
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @author :jiaolian
* @date :Created in 2021-01-28 15:44
* @description:ReentrantReadWriteLock读写锁降级测试
* @modified By:
* 公众号:叫练
*/
public class WriteLockLowerTest {
private static final ReentrantReadWriteLock REENTRANT_READ_WRITE_LOCK = new ReentrantReadWriteLock();
private static final ReentrantReadWriteLock.WriteLock WRITE_LOCK = REENTRANT_READ_WRITE_LOCK.writeLock();
private static final ReentrantReadWriteLock.ReadLock READ_LOCK = REENTRANT_READ_WRITE_LOCK.readLock();
public static void main(String[] args) {
try {
WRITE_LOCK.lock();
System.out.println("获取写锁");
READ_LOCK.lock();
System.out.println("获取读锁");
} finally {
READ_LOCK.unlock();
System.out.println("释放写锁");
WRITE_LOCK.unlock();
System.out.println("释放读锁");
}
}
}
如上述代码:程序可以运行完毕,说明锁可以降级。另外说一句,上面的程序先获取读锁再获取写锁,程序是会阻塞的,为什么呢?欢迎小伙伴在留言区写下评论!
总结
今天我们用通俗易懂的文字描述了ReentrantReadWriteLock读写锁。喜欢的请点赞加评论哦!点关注,不迷路,我是叫练【公众号】,边叫边练。期待我们下次再见!

ReentrantReadWriteLock读写锁简单原理案例证明的更多相关文章
- java多线程:并发包中ReentrantReadWriteLock读写锁的原理
一:读写锁解决的场景问题--->数据的读取频率远远大于写的频率的场景,就可以使用读写锁.二:读写锁的结构--->用state一个变量.将其转化成二进制,前16位为高位,标记读线程获取锁的次 ...
- Java并发包源码学习系列:ReentrantReadWriteLock读写锁解析
目录 ReadWriteLock读写锁概述 读写锁案例 ReentrantReadWriteLock架构总览 Sync重要字段及内部类表示 写锁的获取 void lock() boolean writ ...
- ReentrantReadWriteLock读写锁的使用
Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象.两个线程执行的代码片段要实现同步互斥的效果,它们必须用同一个Lock对象. 读写锁:分为读 ...
- ReentrantReadWriteLock读写锁的使用2
本文可作为传智播客<张孝祥-Java多线程与并发库高级应用>的学习笔记. 这一节我们做一个缓存系统. 在读本节前 请先阅读 ReentrantReadWriteLock读写锁的使用1 第一 ...
- 锁对象-Lock: 同步问题更完美的处理方式 (ReentrantReadWriteLock读写锁的使用/源码分析)
Lock是java.util.concurrent.locks包下的接口,Lock 实现提供了比使用synchronized 方法和语句可获得的更广泛的锁定操作,它能以更优雅的方式处理线程同步问题,我 ...
- java并发锁ReentrantReadWriteLock读写锁源码分析
1.ReentrantReadWriterLock 基础 所谓读写锁,是对访问资源共享锁和排斥锁,一般的重入性语义为如果对资源加了写锁,其他线程无法再获得写锁与读锁,但是持有写锁的线程,可以对资源加读 ...
- 【分布式锁】07-Zookeeper实现分布式锁:Semaphore、读写锁实现原理
前言 前面已经讲解了Zookeeper可重入锁的实现原理,自己对分布式锁也有了更深的认知. 我在公众号中发了一个疑问,相比于Redis来说,Zookeeper的实现方式要更好一些,即便Redis作者实 ...
- java中ReentrantReadWriteLock读写锁的使用
Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象.两个线程执行的代码片段要实现同步互斥的效果,它们必须用同一个Lock对象. 读写锁:分为读 ...
- ReentrantReadWriteLock读写锁的使用1
本文可作为传智播客<张孝祥-Java多线程与并发库高级应用>的学习笔记. 一个简单的例子 两个线程,一个不断打印a,一个不断打印b public class LockTest { publ ...
随机推荐
- 转载--对batch normalization的理解
转载的大神的: https://www.cnblogs.com/guoyaohua/p/8724433.html 上边这个应该是抄的下边这个原文,但是上边的有重点标记 https://blog.csd ...
- HTTP高级(Cookie,Session ,LocalStorage )
Cookie 服务器通过 Set-Cookie 头给客户端一串字符串 客户端每次访问相同域名的网页时,必须带上这段字符串 客户端要在一段时间内保存这个Cookie Cookie 默认在用户关闭页面后就 ...
- Java GC --- Java堆内存
Java堆是被所有线程共享的一块内存区域,所有对象实例和数组都在堆上进行内存分配.为了进行高效的垃圾回收,虚拟机把堆内存划分成: 1. 新生代(Young Generation): 由 Eden 与 ...
- ZooKeeper集群“脑裂”
ZooKeeper 集群节点为什么要部署成奇数ZooKeeper 容错指的是:当宕掉几个ZooKeeper节点服务器之后,剩下的个数必须大于宕掉的个数,也就是剩下的节点服务数必须大于n/2,这样Zoo ...
- #2020征文-TV# Tab切换选项卡同时更换内容
Tab选项卡是应用程序中最最常用,也是最普遍存在的一种布局形态,无论是在PC端还是在移动端,都是一种清晰明了,层级关系简单的,能够使用户明确所处位置.Tab选项卡可以置于页面的底部,比如微信底部选项卡 ...
- 从零开始的C程序设计大作业——学生成绩管理系统
前言 学生成绩管理系统可以说是C语言程序设计的结课的必备大作业了.花了些时间,费了些头发肝了下,完成了两个系统,一个是控制台版本的,另一个用easyx图形库进行了优化. 先放出完成后的演示图片占个坑. ...
- 【剑指 Offer】11.旋转数组的最小数字
题目描述 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转.输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素.例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的 ...
- MySQL中的全局锁和表级锁
全局锁和表锁 数据库锁设计的初衷是解决并发出现的一些问题.当出现并发访问的时候,数据库需要合理的控制资源的访问规则.而锁就是访问规则的重要数据结构. 根据锁的范围,分为全局锁.表级锁和行级锁三类. 全 ...
- 如何实现CentOS服务器的扩容??
Linux的硬盘识别: 一般使用"fdisk -l"命令可以列出系统中当前连接的硬盘 设备和分区信息.新硬盘没有分区信息,则只显示硬盘大小信息. 1.关闭服务器加上新硬盘 2.启动 ...
- 十一:WEB渗透必懂知识点
简述WEB层面上的漏洞以及类型,具体漏洞的危害等级, 如何形成以及如何发现 右边权重大于左边 CTF,SRC,红蓝对抗,实战 简要说明以上漏洞危害 简要说课以上漏洞等级划分 简要说明以上漏洞重点内容 ...