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写的过程中,在对写的线程是同步的,因此我们可以得出一个结论:读写锁是读读共享,读写同步

独占获取锁简单流程




如上图,我们简单的梳理下独占获取锁流程。

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

共享获取锁简单流程




如上图,我们简单的梳理下共享锁获取锁流程。

  1. 独占锁获取(上述例子中的READ_LOCK读锁),首先判断是否有线程获取了锁。
  2. 有读锁:当前线程发现此时读锁状态被占用,说明有线程获取了读锁。该线程通过cas自旋【死循环】获取到读锁为止。
  3. 有写锁:还需要判断持有写锁的线程是否是自己,如果是自己而且此时是获取的是读锁会获取锁成功,我们称为锁降级,如果不是自己说明此时有其他线程获取了写锁,那么当前线程需要排队阻塞。
  4. 无锁:直接获取锁。

写锁降级


我们说读写互斥,但同一个线程中,先写后读也是允许的,我们称之为锁降级。在面试中共享锁面试频率也比较高,方便理解我们举个简单的案例说明下。

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读写锁简单原理案例证明的更多相关文章

  1. java多线程:并发包中ReentrantReadWriteLock读写锁的原理

    一:读写锁解决的场景问题--->数据的读取频率远远大于写的频率的场景,就可以使用读写锁.二:读写锁的结构--->用state一个变量.将其转化成二进制,前16位为高位,标记读线程获取锁的次 ...

  2. Java并发包源码学习系列:ReentrantReadWriteLock读写锁解析

    目录 ReadWriteLock读写锁概述 读写锁案例 ReentrantReadWriteLock架构总览 Sync重要字段及内部类表示 写锁的获取 void lock() boolean writ ...

  3. ReentrantReadWriteLock读写锁的使用

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

  4. ReentrantReadWriteLock读写锁的使用2

    本文可作为传智播客<张孝祥-Java多线程与并发库高级应用>的学习笔记. 这一节我们做一个缓存系统. 在读本节前 请先阅读 ReentrantReadWriteLock读写锁的使用1 第一 ...

  5. 锁对象-Lock: 同步问题更完美的处理方式 (ReentrantReadWriteLock读写锁的使用/源码分析)

    Lock是java.util.concurrent.locks包下的接口,Lock 实现提供了比使用synchronized 方法和语句可获得的更广泛的锁定操作,它能以更优雅的方式处理线程同步问题,我 ...

  6. java并发锁ReentrantReadWriteLock读写锁源码分析

    1.ReentrantReadWriterLock 基础 所谓读写锁,是对访问资源共享锁和排斥锁,一般的重入性语义为如果对资源加了写锁,其他线程无法再获得写锁与读锁,但是持有写锁的线程,可以对资源加读 ...

  7. 【分布式锁】07-Zookeeper实现分布式锁:Semaphore、读写锁实现原理

    前言 前面已经讲解了Zookeeper可重入锁的实现原理,自己对分布式锁也有了更深的认知. 我在公众号中发了一个疑问,相比于Redis来说,Zookeeper的实现方式要更好一些,即便Redis作者实 ...

  8. java中ReentrantReadWriteLock读写锁的使用

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

  9. ReentrantReadWriteLock读写锁的使用1

    本文可作为传智播客<张孝祥-Java多线程与并发库高级应用>的学习笔记. 一个简单的例子 两个线程,一个不断打印a,一个不断打印b public class LockTest { publ ...

随机推荐

  1. [LeetCode]231. Power of Two判断是不是2\3\4的幂

    /* 用位操作,乘2相当于左移1位,所以2的幂只有最高位是1 所以问题就是判断你是不是只有最高位是1,怎判断呢 这些数-1后形成的数,除了最高位,后边都是1,如果n&n-1就可以判断了 如果是 ...

  2. Trick:如何去掉html标签点击时的蓝色边框

    我们在用html标签时,如input.button.select,img标签时,点击标签经常出现一个蓝色的边框,这个边框真的很low,想要去掉怎么办   其实,css有样式可以设置一下,这个问题就轻松 ...

  3. AOP的姿势之 简化混用 MemoryCache 和 DistributedCache 的方式

    0. 前言 之前写了几篇文章介绍了一些AOP的知识, 但是还没有亮出来AOP的姿势, 也许姿势漂亮一点, 大家会对AOP有点兴趣 内容大致会分为如下几篇:(毕竟人懒,一下子写完太累了,没有动力) AO ...

  4. 学习一下 SpringCloud (三)-- 服务调用、负载均衡 Ribbon、OpenFeign

    (1) 相关博文地址: 学习一下 SpringCloud (一)-- 从单体架构到微服务架构.代码拆分(maven 聚合): https://www.cnblogs.com/l-y-h/p/14105 ...

  5. spark使用idea向yarn提交报错:Exception in thread "main" java.lang.NoClassDefFoundError: com/sun/jersey/api/client/config/ClientConfig

    解决方法: 找到1.19版本放到spark的jars目录下

  6. 树莓派(4B)新手入门教程

    前期准备 必要物料 树莓派4B 主机 Type-C 电源 内存卡(8G+) 一般建议一步到位64G 系统镜像 镜像写入工具 下载地址 镜像下载 官方下载地址: https://www.raspberr ...

  7. i5 11300H 怎么样 相当于什么水平

    i5-11300H 为 4 核 8 线程,主频 3.1GHz,睿频 4.4GHz,三级缓存 8MBi5-11300H 怎么样看完你就知道了 https://list.jd.com/list.html?

  8. 使用python做一个IRC在线下载器

    使用python做一个IRC在线下载器 1.开发流程 2.软件流程 3.开始 3.0 准备工作 3.1寻找API接口 3.2 文件模块 3.2.1 选择文件弹窗 3.2.2 提取文件名 3.2.2.1 ...

  9. 剑指offer 面试题9:用两个栈实现队列

    题目描述 用两个栈来实现一个队列,完成队列的Push和Pop操作. 队列中的元素为int类型. 使用栈实现队列的下列操作:push(x) -- 将一个元素放入队列的尾部.pop() -- 从队列首部移 ...

  10. 详细的String源码解析

    我们常常把String类型的字符串作为HashMap的key,为什么要这样做呢? 因为String是不可变的,一旦初始化就不再改变了,如果被修改将会是一个新对象. @Test public void ...