ReentrantReadWriteLock读写锁简单原理案例证明

作者: 叫练

ReentrantReadWriteLock存在原因?


们知道List的实现类ArrayList,LinkedList都是非线程安全的,Vector类通过用synchronized修饰方法保证了List的多线程非安全问题,但是有个缺点:读写同步,效率低下 。于是就出现了OnWriteArrayList,它通过写时复制数组实现了读写分离,提高了多线程对List读的效率,适合多读少些的情况。同理:们知道ReentrantLock,它是一把独占的锁,是用来控制线程同步的,如果们用ReentrantLock来实现ArrayList安全,能否达到OnWriteArrayList同样的效果呢?

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();
                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();
                WRITE_LOCK.unlock();
            }
        }
    }
    public static void main(String[] args) {
        MyList myList = new MyList();
        //读写锁适合多读少写情况
        //新建10个读线程,1个写线程
        new Thread(()->,"写线程").start();
        for (int i=0; i<10; i++) {
            new Thread(()->,"读线程"+(i+1)).start();
        }
    }
}

上面案例们用ReentrantReadWriteLock实现了OnWriteArrayList,主线程新建了1个写线程写list,10个读线程读list,程序一共花费2执行完毕,如果用Vector需要花费11秒。在多线程的情况下,通过读写锁操作List,提高了List的读效率,在List读的部分,线程是共享的,在对List写的过程中,在对写的线程是同步的,因此们可以得出一个结论:读写锁是读读共享,读写同步

独占获取锁简单流程


image.png

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

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

共享获取锁简单流程


image.png

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

  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("获取读锁");
            READ_LOCK.unlock();
            System.out.println("释放写锁");
            WRITE_LOCK.unlock();
            System.out.println("释放读锁");
        }
    }
}

如上述代码:程序可以运行完毕,说明锁可以降级。另外说一句,上面的程序先获取读锁再获取写锁,程序是会阻塞的,为什么呢?欢迎小伙伴在留言区写下评论!

总结


今天们用通俗易懂的文字描述了ReentrantReadWriteLock 读写锁。喜欢的请点赞加评论哦!点关注,不迷路,是叫练【公众号】,边叫边练。期待们下次再见!
tempimage1611629165941.gif

[] ) [](top

原文创作:叫练

原文链接:https://www.cnblogs.com/jiaolian/p/14353892.html

文章列表

更多推荐

更多
  • Azure上Linux管理-十、使用 Azure Kubernetes 服务 技术要求,开始使用 AKS,与 Helm 一起工作,使用草稿,管理 Kubernetes,问题,进一步,使用 WSL 和 VS Code,安装依赖,kubectl 安装,使用 Azure CLI 创建集群,AKS 首次部署,创建服务,多
    Apache CN

  • Azure上Linux管理-十一、故障排除和监控您的工作负载 module(load="imuxsock"),技术要求,访问您的系统,Azure 日志分析,性能监控,问题,进一步,不允许远程访问,正在端口上工作,使用 nftables,引导诊断,Linux 登录,配置日志分析服务,安装 Azure
    Apache CN

  • Azure上Linux管理-十二、附录 第一章:探索微软 Azure 云,第二章:Azure 云入门,第三章:Linux 基础管理,第 4 章:管理 Azure,第五章:高级 Linux 管理,第七章:部署虚拟机,第八章:探索连续配置自动化,第 9 章:Azure 中的容器虚
    Apache CN

  • Azure上Linux管理-九、Azure 中的容器虚拟化 cloudconfig,集装箱技术导论,系统生成,Docker,Azure 容器实例,Buildah, Podman, and Skopeo,容器和储存,问题,进一步,容器历史,chroot 环境,OpenVZ,LXC,创建一个带启动的
    Apache CN

  • Azure上Linux管理-七、部署你的虚拟机 ResourceGroup 不存在,创建它:,vnet 不存在,创建 vnet,cloudconfig,Vagrant.config 结束,部署场景,Azure 中的自动部署选项,初始配置,流浪,封隔器,自定义虚拟机和 vhd,问题,进
    Apache CN

  • Azure上Linux管理-八、探索持续配置自动化 了解配置管理,使用 Ansible,使用地球形态,使用 PowerShell DSC,Azure 策略客户端配置,其他解决方案,问题,进一步,技术要求,Ansible 的安装,SSH 配置,裸最小配置,库存文件,Ansible 剧本和模
    Apache CN

  • Azure上Linux管理-五、高级 Linux 管理 技术要求,软件管理,网络,存储,systemd,问题,进一步,RPM 软件管理器,YUM 软件管理,基于 DNF 的软件管理,DPKG 软件管理器,运用 apt 进行软件管理,ZYpp 软件管理,识别网络接口,IP 地址识别,显示路由表
    Apache CN

  • Azure上Linux管理-六、管理 Linux 安全与身份 SELINUX=可以接受以下三个值之一:,permissiveSELinux 打印警告而不是强制执行。,disabled 没有加载 SELinux 策略。,SELINUXTYPE=可以接受以下三个值之一:,targeted 目标进程
    Apache CN

  • Azure上Linux管理-四、管理 Azure 使用 Azure CLI 和 PowerShell 管理 Azure 资源,技术要求,管理存储资源,管理网络资源,管理计算资源,虚拟机资源,问题,进一步,存储帐户,托管磁盘,Azure 文件,Azure Blob,虚拟网络,子网,网络安
    Apache CN

  • Azure上Linux管理-三、Linux 基础管理 Linux Shell,获取帮助,使用文本文件,在文件系统中找到你的方式,流程管理,自由访问控制,问题,执行命令,命令行编辑,与历史一起工作,自动完成,球状,重定向,处理变量,Bash 配置文件,使用手册页,使用信息文档,其他文档,文本
    Apache CN

  • 近期文章

    更多
    文章目录

      推荐作者

      更多