您当前的位置:首页 > 电脑百科 > 程序开发 > 编程百科

一文理解什么是读写锁分离?如何实现读写锁?

时间:2023-10-23 15:24:57  来源:  作者:从程序员到架构师
一文理解什么是读写锁分离?如何实现读写锁?

 

场景描述

相信很多开发者都遇到过多线程并发访问共享资源的情况,那么为了防止在多线程访问的时候出现线程安全问题,通常我们可以使用加锁的方式来避免线程安全问题,可以使用synchronized关键字以及一些显式锁操作,当然在分布式场景中还可以使用一些分布式锁机制来保证共享资源线程安全访问。

对于共享资源的访问,可以分为两种操作,一种是对共享资源的读操作,一种是对共享资源的写操作。如果有多个线程在同一时刻对共享资源的操作都是读操作的话,虽然是存在资源竞争的情况,但是并不会影响到最终的访问结果,也不会引起数据不一致的情况发生。这样时候,如果还是使用排他锁的方式进行加锁,很明显就有点得不偿失了。如下图所示,共享资源被多个线程共同进行读操作的时候,是不会出现线程安全问题的。

一文理解什么是读写锁分离?如何实现读写锁?

 

所以,如果在某个场景中,对于共享资源的读操作要明显多于写操作的时候,这个时候,读操作是可以不用进行加锁的。这样对系统的性能的提升也会非常明显。

如图所示,在多个线程对共享资源进行写操作的时候,很明显,一定会造成资源的数据一致性问题出现。所以对于写操作来讲,一定要注意进行加锁的操作。

读写分离程序实现

第一步、定义一个锁接口

无论是对于读锁还是写锁,都离不开加锁和解锁两个操作,所以在接口中需要定义两个基本的操作,加锁和解锁,代码如下。

public interface Lock {
    /**
     * 进行加锁操作
     * @throws InterruptedException
     */
    void lock() throws InterruptedException;

    /**
     * 进行解锁操作
     */
    void unlock();
}

第二步、读写锁接口实现

在定义好锁接口之后,接下来就是需要去定义如何来操作锁。既然需要对读写锁进行判断,那么首先就需要有关于锁的判断条件的规范,下面实现的ReadWriteLock其实就是读写锁的使用规范。

public interface ReadWriteLock {
    /**
     * 创建读锁
     * @return
     */
    Lock readLock();
    /**
     * 创建写锁
     * @return
     */
    Lock writeLock();
    /**
     * 获取正在执行的写操作个数
     * @return
     */
    int getDoingWriters();
    /**
     * 获取正在等待执行的写操作个数
     * @return
     */
    int getWAItingWriters();
    /**
     * 获取正在执行的读操作个数
     * @return
     */
    int getDoingReader();


    static ReadWriteLock readWriteLock(){
        return new ReadWriteLockImpl();
    }

    static ReadWriteLock readWriteLock(boolean preferWriter){
        return new ReadWriteLockImpl(preferWriter);
    }

}

会看到,在上面这个接口中定义了如下的一些操作

  • 创建读锁:readLock()
  • 创建写锁:writeLock()
  • 获取正在执行的读操作:getDoingReader()
  • 获取正在执行的写操作:getDoingWriters()
  • 获取等待执行的写操作:getWaitingWriters()

为什么要有这些操作呢?其实这样写操作就是来规定如何去使用读写锁的,例如,读操作的个数大于0的时候,这个适合就意味着写操作的个数是等于0的。反之当写操作的个数大于0 的时候,就意味着读操作的个数是等于0的。因为在读写操作的过程中,读操作和写操作是冲突的过程,也就是说在写的时候不能读,在读的时候不能写。那么通过这样的一个数量关系我们就可以实现什么时候加锁什么时候解锁了。

第三步、按照规则实现读写锁。

既然有了上面的规则的定义,那么一定要有规则的实现,下面这段代码其实就是对上面的规则的实现。

public class ReadWriteLockImpl implements ReadWriteLock {
    /**
     * 定义锁对象
     */
    private final Object MUTEX = new Object();

    private int doingWriters = 0;

    private int waitingWriters = 0;

    private int doingReaders = 0;
    /**
     * 偏好设置
     */
    private boolean preferWriter;
    public ReadWriteLockImpl() {
        this(true);
    }
    public ReadWriteLockImpl(boolean preferWriter) {
        this.preferWriter = preferWriter;
    }
    @Override
    public Lock readLock() {
        return new ReadLock(this);
    }
    @Override
    public Lock writeLock() {
        return new WriteLock(this);
    }
    void addDoingWriters(){
        this.doingWriters++;
    }
    void addWaitingWriters(){
        this.waitingWriters++;
    }
    void addDoingReader(){
        this.doingReaders++;
    }
    void minusDoingWriters(){
        this.doingWriters--;
    }
    void minusWaitingWriters(){
        this.waitingWriters--;
    }
    void  minusDoingReader(){
        this.doingReaders--;
    }
    @Override
    public int getDoingWriters() {
        return this.doingWriters;
    }
    @Override
    public int getWaitingWriters() {
        return this.waitingWriters;
    }
    @Override
    public int getDoingReader() {
        return this.doingReaders;
    }
    Object getMutex(){
        return this.MUTEX;
    }
    boolean getPreferWriter(){
        return this.preferWriter;
    }
    void changePrefer(boolean preferWriter){
        this.preferWriter = preferWriter;
    }
}

上述代码中包含了如下的一些内容

  • addDoingWriters() :正在执行的写操作加一;
  • addWaitingWriters():等待执行的写操作加一;
  • addDoingReader():正在执行的读操作加一;
  • minusDoingWriters():正在执行的写操作减一;
  • minusWaitingWriters():等待执行的写操作减一;
  • minusDoingReader():正在执行的读操作减一;
  • changePrefer(boolean preferWriter):修改偏好设置;
  • Object getMutex():获取锁对象;

其他的操作可以先不做了解,这里重要的部分有两部分内容,第一是对于等待读写操作的增加和减少;第二则是对Object getMutex()操作的立即。

对于读写操作数量的判断主要是用来进行加锁和解锁操作的判断。那么Object getMutex()是用来干什么的?

Object getMutex()操作是用来获取一个锁对象,那么我们真正使用的ReadLock 和 WriterLock又是干嘛的?其实细心的读者可能发现了Object getMutex()锁操作其实是为了保证读写操作内部的一个线程安全,也就是为了保证我们对于加锁条件判断的一个线程安全性的保证,而真正我们通过readLock()方法和writeLock()方法进行的读写操作实现才是读写锁的重点。

实现读锁

根据上面的规则,读锁的条件是当前没有再执行的写操作的时候就可以进行加锁,当前没有再有执行的读操作的时候就可以释放锁。根据规则实现,代码如下。

public class ReadLock implements Lock {

    private final ReadWriteLockImpl readWriteLock;

    public ReadLock(ReadWriteLockImpl readWriteLock) {
        this.readWriteLock = readWriteLock;
    }
  /***
  * 进行加锁操作
  */
    @Override
    public void lock() throws InterruptedException {
        synchronized (readWriteLock.getMutex()){
            while (readWriteLock.getDoingWriters()>0
                   ||(readWriteLock.getPreferWriter()
                      &&readWriteLock.getWaitingWriters()>0)){
                readWriteLock.getMutex().wait();
            }
            readWriteLock.addDoingReader();
        }
    }

		/**
		* 进行解锁操作
		*/
    @Override
    public void unlock() {
        synchronized (readWriteLock.getMutex()){
            readWriteLock.minusDoingReader();
            readWriteLock.changePrefer(true);
            readWriteLock.getMutex().notifyAll();
        }
    }
}

实现写锁

写锁实现的条件根据上面的描述可以知道,加锁的条件是当前没有读操作,解锁的条件是当前没有正在执行的写操作。代码实现如下。

public class WriteLock implements Lock {

    private final ReadWriteLockImpl readWriteLock;

    public WriteLock(ReadWriteLockImpl readWriteLock) {
        this.readWriteLock = readWriteLock;
    }

    /**
     * 进行加锁操作
     * @throws InterruptedException
     */
    @Override
    public void lock() throws InterruptedException {

        synchronized (readWriteLock.getMutex()){
            try{
                readWriteLock.addWaitingWriters();
                while (readWriteLock.getDoingReader()>0
                       ||readWriteLock.getDoingWriters()>0){
                    readWriteLock.getMutex().wait();
                }
            }finally {
                this.readWriteLock.minusWaitingWriters();
            }
            readWriteLock.addDoingWriters();
        }
    }

    /**
     * 进行解锁操作
     */
    @Override
    public void unlock() {
        synchronized (readWriteLock.getMutex()){
            readWriteLock.minusDoingWriters();
            readWriteLock.changePrefer(true);
            readWriteLock.getMutex().notifyAll();
        }
    }
}

分析

根据读写锁分别的实现来看,似乎底层锁定的就是在ReadWriteLockImpl规则中实现的final Object MUTEX = new Object()锁定对象,但是仔细想来,这个对象其实就是为了保证读写锁的安全性,真正能够行决定读写锁的其实是ReadWriteLockImpl对象中定义一些数量规则。通过这些数量规则的判断决定是读操作还是写操作。

另外在JDK并发包
JAVA.util.concurrent.locks中提供了一些读写锁的操作,如下图所示。

一文理解什么是读写锁分离?如何实现读写锁?

 

通过图中所提供的方法来看,基本的实现思路与上面我们的实现思路是一样。我们采取的是偏向设置,而JDK提供的是公平性相关的内容。而关于公平性相关的内容,这里我们不做过多的介绍,有兴趣的读者可以自己研究一下啊。



Tags:读写锁   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
一文理解什么是读写锁分离?如何实现读写锁?
场景描述相信很多开发者都遇到过多线程并发访问共享资源的情况,那么为了防止在多线程访问的时候出现线程安全问题,通常我们可以使用加锁的方式来避免线程安全问题,可以使用syn...【详细内容】
2023-10-23  Search: 读写锁  点击:(199)  评论:(0)  加入收藏
Golang中的互斥锁Mutex与读写锁RWMutex详解
Golang中的sync包实现了两种锁:互斥锁(Mutex)和读写锁(RWMutex)。互斥锁(sync.Mutex) 使用Lock方法加锁,使用Unlock方法解锁,Golang从1.18新增了TryLock方法,用于尝试获取锁,返回成功或...【详细内容】
2023-05-15  Search: 读写锁  点击:(364)  评论:(0)  加入收藏
详解linux多线程——互斥锁、条件变量、读写锁、自旋锁、信号量
x一、互斥锁(同步)  在多任务操作系统中,同时运行的多个任务可能都需要使用同一种资源。这个过程有点类似于,公司部门里,我在使用着打印机打印东西的同时(还没有打印完),...【详细内容】
2020-07-17  Search: 读写锁  点击:(359)  评论:(0)  加入收藏
Go语言中互斥锁与读写锁,你知多少?
简述Golang中的锁机制主要包含互斥锁和读写锁互斥锁互斥锁是传统并发程序对共享资源进行控制访问的主要手段。在Go中主要使用 sync.Mutex的结构体表示。一个简单的示例:func...【详细内容】
2019-12-03  Search: 读写锁  点击:(454)  评论:(0)  加入收藏
什么是读写锁?微服务注册中心是如何进行读写锁优化的?
本文我们来聊一下读写锁。所谓的读写锁,就是将一个锁拆分为读锁和写锁两个锁,然后加锁的时候,可以加写锁,也可以加读锁。...【详细内容】
2019-11-14  Search: 读写锁  点击:(571)  评论:(0)  加入收藏
比较:读写锁和互斥锁
相交进程之间的关系主要有两种,同步与互斥。所谓互斥,是指散步在不同进程之间的若干程序片断,当某个进程运行其中一个程序片段时,其它进程就不能运行它 们之中的任一程序片段,只...【详细内容】
2019-08-05  Search: 读写锁  点击:(1045)  评论:(0)  加入收藏
▌简易百科推荐
即将过时的 5 种软件开发技能!
作者 | Eran Yahav编译 | 言征出品 | 51CTO技术栈(微信号:blog51cto) 时至今日,AI编码工具已经进化到足够强大了吗?这未必好回答,但从2023 年 Stack Overflow 上的调查数据来看,44%...【详细内容】
2024-04-03    51CTO  Tags:软件开发   点击:(5)  评论:(0)  加入收藏
跳转链接代码怎么写?
在网页开发中,跳转链接是一项常见的功能。然而,对于非技术人员来说,编写跳转链接代码可能会显得有些困难。不用担心!我们可以借助外链平台来简化操作,即使没有编程经验,也能轻松实...【详细内容】
2024-03-27  蓝色天纪    Tags:跳转链接   点击:(12)  评论:(0)  加入收藏
中台亡了,问题到底出在哪里?
曾几何时,中台一度被当做“变革灵药”,嫁接在“前台作战单元”和“后台资源部门”之间,实现企业各业务线的“打通”和全域业务能力集成,提高开发和服务效率。但在中台如火如荼之...【详细内容】
2024-03-27  dbaplus社群    Tags:中台   点击:(8)  评论:(0)  加入收藏
员工写了个比删库更可怕的Bug!
想必大家都听说过删库跑路吧,我之前一直把它当一个段子来看。可万万没想到,就在昨天,我们公司的某位员工,竟然写了一个比删库更可怕的 Bug!给大家分享一下(不是公开处刑),希望朋友们...【详细内容】
2024-03-26  dbaplus社群    Tags:Bug   点击:(5)  评论:(0)  加入收藏
我们一起聊聊什么是正向代理和反向代理
从字面意思上看,代理就是代替处理的意思,一个对象有能力代替另一个对象处理某一件事。代理,这个词在我们的日常生活中也不陌生,比如在购物、旅游等场景中,我们经常会委托别人代替...【详细内容】
2024-03-26  萤火架构  微信公众号  Tags:正向代理   点击:(10)  评论:(0)  加入收藏
看一遍就理解:IO模型详解
前言大家好,我是程序员田螺。今天我们一起来学习IO模型。在本文开始前呢,先问问大家几个问题哈~什么是IO呢?什么是阻塞非阻塞IO?什么是同步异步IO?什么是IO多路复用?select/epoll...【详细内容】
2024-03-26  捡田螺的小男孩  微信公众号  Tags:IO模型   点击:(8)  评论:(0)  加入收藏
为什么都说 HashMap 是线程不安全的?
做Java开发的人,应该都用过 HashMap 这种集合。今天就和大家来聊聊,为什么 HashMap 是线程不安全的。1.HashMap 数据结构简单来说,HashMap 基于哈希表实现。它使用键的哈希码来...【详细内容】
2024-03-22  Java技术指北  微信公众号  Tags:HashMap   点击:(11)  评论:(0)  加入收藏
如何从头开始编写LoRA代码,这有一份教程
选自 lightning.ai作者:Sebastian Raschka机器之心编译编辑:陈萍作者表示:在各种有效的 LLM 微调方法中,LoRA 仍然是他的首选。LoRA(Low-Rank Adaptation)作为一种用于微调 LLM(大...【详细内容】
2024-03-21  机器之心Pro    Tags:LoRA   点击:(12)  评论:(0)  加入收藏
这样搭建日志中心,传统的ELK就扔了吧!
最近客户有个新需求,就是想查看网站的访问情况。由于网站没有做google的统计和百度的统计,所以访问情况,只能通过日志查看,通过脚本的形式给客户导出也不太实际,给客户写个简单的...【详细内容】
2024-03-20  dbaplus社群    Tags:日志   点击:(4)  评论:(0)  加入收藏
Kubernetes 究竟有没有 LTS?
从一个有趣的问题引出很多人都在关注的 Kubernetes LTS 的问题。有趣的问题2019 年,一个名为 apiserver LoopbackClient Server cert expired after 1 year[1] 的 issue 中提...【详细内容】
2024-03-15  云原生散修  微信公众号  Tags:Kubernetes   点击:(5)  评论:(0)  加入收藏
站内最新
站内热门
站内头条