ReentrantReadWriteLock读写锁及其在 RxCache 中的使用

2024-01-08 22:58

本文主要是介绍ReentrantReadWriteLock读写锁及其在 RxCache 中的使用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

640?wx_fmt=jpeg


一. ReentrantReadWriteLock读写锁

Lock 是相当于 synchronized 更面向对象的同步方式,ReentrantLock 是 Lock 的实现。

本文要介绍的 ReentrantReadWriteLock 跟 ReentrantLock 并没有直接的关系,因为它们之间没有继承和实现的关系。

但是 ReentrantReadWriteLock 拥有读锁(ReadLock)和写锁(WriteLock),它们分别都实现了 Lock。

 
  1.    /** Inner class providing readlock */

  2.    private final ReentrantReadWriteLock.ReadLock readerLock;

  3.    /** Inner class providing writelock */

  4.    private final ReentrantReadWriteLock.WriteLock writerLock;

ReentrantReadWriteLock 在使用读锁时,其他线程可以进行读操作,但不可进行写操作。ReentrantReadWriteLock 在使用写锁时,其他线程读、写操作都不可以。ReentrantReadWriteLock 能够兼顾数据操作的原子性和读写的性能。

1.1 公平锁和非公平锁

从 ReentrantReadWriteLock 的构造函数中可以看出,它默认使用了非公平锁。

 
  1.    /**

  2.     * Creates a new {@code ReentrantReadWriteLock} with

  3.     * default (nonfair) ordering properties.

  4.     */

  5.    public ReentrantReadWriteLock() {

  6.        this(false);

  7.    }


  8.    /**

  9.     * Creates a new {@code ReentrantReadWriteLock} with

  10.     * the given fairness policy.

  11.     *

  12.     * @param fair {@code true} if this lock should use a fair ordering policy

  13.     */

  14.    public ReentrantReadWriteLock(boolean fair) {

  15.        sync = fair ? new FairSync() : new NonfairSync();

  16.        readerLock = new ReadLock(this);

  17.        writerLock = new WriteLock(this);

  18.    }

在 Java 中所谓公平锁是指,每个线程在获取锁时,会先查看此锁维护的等待队列,如果为队列空或者当前线程线程是等待队列的第一个,则占有锁。否则就会加入到等待队列中,以后按照 FIFO 的顺序从队列中取出。

非公平锁在获取锁时,不会遵循 FIFO 的顺序,而是直接尝试获取锁。如果获取不到锁,则像公平锁一样自动加入到队列的队尾等待。

非公平锁的性能要高于公平锁。

1.2 读锁

读锁是一个共享锁。读锁是 ReentrantReadWriteLock 的内部静态类,它的 lock()、trylock()、unlock() 都是委托 Sync 类实现。

Sync 是真正实现读写锁功能的类,它继承自 AbstractQueuedSynchronizer 。

写锁

写锁是一个排他锁。写锁也是 ReentrantReadWriteLock 的内部静态类,它的 lock()、trylock()、unlock() 也都是委托 Sync 类实现。写锁的代码类似于读锁,但是在同一时刻写锁是不能被多个线程所获取,它是独占式锁。

写锁可以降级成读锁,下面会介绍锁降级。

1.3 锁降级

锁降级是指先获取写锁,再获取读锁,然后再释放写锁的过程 。锁降级是为了保证数据的可见性。锁降级是 ReentrantReadWriteLock 重要特性之一。

值得注意的是,ReentrantReadWriteLock 并不能实现锁升级。

二. RxCache 中使用读写锁

RxCache 是一款支持 Java 和 Android 的 Local Cache 。目前,支持堆内存、堆外内存(off-heap memory)、磁盘缓存。

github地址:https://github.com/fengzhizi715/RxCache

RxCache 的 CacheRepository 类实现了缓存操作的类,它使用了 ReentrantReadWriteLock 用于保证缓存在读写时避免出现多线程的并发问题。

首先,创建一个读写锁,并获得读锁、写锁的实例。

 
  1. class CacheRepository {


  2.    private Memory memory;

  3.    private Persistence persistence;


  4.    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

  5.    private final Lock readLock = lock.readLock();

  6.    private final Lock writeLock = lock.writeLock();


  7.    ......

  8. }

在缓存的读操作时,使用读锁。

 
  1.    boolean containsKey(String key) {


  2.        try {

  3.            readLock.lock();


  4.            if (Preconditions.isBlank(key)) return false;


  5.            return (memory != null && memory.containsKey(key)) || (persistence != null && persistence.containsKey(key));


  6.        } finally {


  7.            readLock.unlock();

  8.        }

  9.    }

在缓存的写操作时,使用写锁。

 
  1.    void remove(String key) {


  2.        try {

  3.            writeLock.lock();


  4.            if (Preconditions.isNotBlank(key)) {


  5.                if (memory != null) {

  6.                    memory.evict(key);

  7.                }


  8.                if (persistence != null) {

  9.                    persistence.evict(key);

  10.                }

  11.            }


  12.        } finally {


  13.            writeLock.unlock();

  14.        }

  15.    }

对于某一个方法,如果在读操作做完之后要进行写操作,则需要先释放读锁,再获取写锁(否则会死锁)。写操作之后,还需要进行读操作的话,可以使用锁降级。

 
  1.    <T> Record<T> get(String key, Type type, CacheStrategy cacheStrategy) {


  2.        try {

  3.            readLock.lock();


  4.            Record<T> record = null;


  5.            if (Preconditions.isNotBlanks(key, type)) {


  6.                switch (cacheStrategy) {


  7.                    case MEMORY: {


  8.                        if (memory!=null) {


  9.                            record = memory.getIfPresent(key);

  10.                        }


  11.                        break;

  12.                    }


  13.                    case PERSISTENCE: {


  14.                        if (persistence!=null) {


  15.                            record = persistence.retrieve(key, type);

  16.                        }


  17.                        break;

  18.                    }


  19.                    case ALL: {


  20.                        if (memory != null) {


  21.                            record = memory.getIfPresent(key);

  22.                        }


  23.                        if (record == null && persistence != null) {


  24.                            record = persistence.retrieve(key, type);


  25.                            if (memory!=null && record!=null && !record.isExpired()) { // 如果 memory 不为空,record 不为空,并且没有过期


  26.                                readLock.unlock(); // 先释放读锁

  27.                                writeLock.lock();  // 再获取写锁


  28.                                try {


  29.                                    if (record.isNeverExpire()) { // record永不过期的话,直接保存不需要计算ttl


  30.                                        memory.put(record.getKey(),record.getData());

  31.                                    } else {


  32.                                        long ttl = record.getExpireTime()- (System.currentTimeMillis() - record.getCreateTime());

  33.                                        memory.put(record.getKey(),record.getData(), ttl);

  34.                                    }


  35.                                    readLock.lock();    // 写锁在没有释放之前,获得读锁 (锁降级)

  36.                                } finally {


  37.                                    writeLock.unlock(); // 释放写锁

  38.                                }

  39.                            }

  40.                        }

  41.                        break;

  42.                    }

  43.                }

  44.            }


  45.            return record;

  46.        } finally {


  47.            readLock.unlock();

  48.        }

  49.    }

三. 总结

ReentrantReadWriteLock 读写锁适用于读多写少的场景,以提高系统的并发性。因此,RxCache 使用读写锁来实现缓存的操作。

RxCache 系列的相关文章:

  1. 堆外内存及其在 RxCache 中的使用

  2. Retrofit 风格的 RxCache及其多种缓存替换算法

  3. RxCache 整合 Android 的持久层框架 greenDAO、Room

  4. 给 Java 和 Android 构建一个简单的响应式Local Cache


关注【Java与Android技术栈】

更多精彩内容请关注扫码

640?wx_fmt=jpeg


这篇关于ReentrantReadWriteLock读写锁及其在 RxCache 中的使用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/585132

相关文章

Java中Runnable和Callable的区别和联系及使用场景

《Java中Runnable和Callable的区别和联系及使用场景》Java多线程有两个重要的接口,Runnable和Callable,分别提供一个run方法和call方法,二者是有较大差异的,本文... 目录一、Runnable使用场景二、Callable的使用场景三、关于Future和FutureTa

使用EasyExcel实现简单的Excel表格解析操作

《使用EasyExcel实现简单的Excel表格解析操作》:本文主要介绍如何使用EasyExcel完成简单的表格解析操作,同时实现了大量数据情况下数据的分次批量入库,并记录每条数据入库的状态,感兴... 目录前言固定模板及表数据格式的解析实现Excel模板内容对应的实体类实现AnalysisEventLis

使用国内镜像源优化pip install下载的方法步骤

《使用国内镜像源优化pipinstall下载的方法步骤》在Python开发中,pip是一个不可或缺的工具,用于安装和管理Python包,然而,由于默认的PyPI服务器位于国外,国内用户在安装依赖时可... 目录引言1. 为什么需要国内镜像源?2. 常用的国内镜像源3. 临时使用国内镜像源4. 永久配置国内镜

Go语言中最便捷的http请求包resty的使用详解

《Go语言中最便捷的http请求包resty的使用详解》go语言虽然自身就有net/http包,但是说实话用起来没那么好用,resty包是go语言中一个非常受欢迎的http请求处理包,下面我们一起来学... 目录安装一、一个简单的get二、带查询参数三、设置请求头、body四、设置表单数据五、处理响应六、超

如何使用C#串口通讯实现数据的发送和接收

《如何使用C#串口通讯实现数据的发送和接收》本文详细介绍了如何使用C#实现基于串口通讯的数据发送和接收,通过SerialPort类,我们可以轻松实现串口通讯,并结合事件机制实现数据的传递和处理,感兴趣... 目录1. 概述2. 关键技术点2.1 SerialPort类2.2 异步接收数据2.3 数据解析2.

详解如何使用Python提取视频文件中的音频

《详解如何使用Python提取视频文件中的音频》在多媒体处理中,有时我们需要从视频文件中提取音频,本文为大家整理了几种使用Python编程语言提取视频文件中的音频的方法,大家可以根据需要进行选择... 目录引言代码部分方法扩展引言在多媒体处理中,有时我们需要从视频文件中提取音频,以便进一步处理或分析。本文

使用Dify访问mysql数据库详细代码示例

《使用Dify访问mysql数据库详细代码示例》:本文主要介绍使用Dify访问mysql数据库的相关资料,并详细讲解了如何在本地搭建数据库访问服务,使用ngrok暴露到公网,并创建知识库、数据库访... 1、在本地搭建数据库访问的服务,并使用ngrok暴露到公网。#sql_tools.pyfrom

使用mvn deploy命令上传jar包的实现

《使用mvndeploy命令上传jar包的实现》本文介绍了使用mvndeploy:deploy-file命令将本地仓库中的JAR包重新发布到Maven私服,文中通过示例代码介绍的非常详细,对大家的学... 目录一、背景二、环境三、配置nexus上传账号四、执行deploy命令上传包1. 首先需要把本地仓中要

Spring Cloud之注册中心Nacos的使用详解

《SpringCloud之注册中心Nacos的使用详解》本文介绍SpringCloudAlibaba中的Nacos组件,对比了Nacos与Eureka的区别,展示了如何在项目中引入SpringClo... 目录Naacos服务注册/服务发现引⼊Spring Cloud Alibaba依赖引入Naco编程s依

Java springBoot初步使用websocket的代码示例

《JavaspringBoot初步使用websocket的代码示例》:本文主要介绍JavaspringBoot初步使用websocket的相关资料,WebSocket是一种实现实时双向通信的协... 目录一、什么是websocket二、依赖坐标地址1.springBoot父级依赖2.springBoot依赖