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

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

一. ReentrantReadWriteLock读写锁



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


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


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

/** Inner class providing readlock */
    private final ReentrantReadWriteLock.ReadLock readerLock;
    /** Inner class providing writelock */
    private final ReentrantReadWriteLock.WriteLock writerLock;


ReentrantReadWriteLock 在使用读锁时,其他线程可以进行读操作,但不可进行写操作。ReentrantReadWriteLock 在使用写锁时,其他线程读、写操作都不可以。


ReentrantReadWriteLock 能够兼顾数据操作的原子性和读写的性能。


1.1 公平锁和非公平锁


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

/**
     * Creates a new {@code ReentrantReadWriteLock} with
     * default (nonfair) ordering properties.
     */
    public ReentrantReadWriteLock() {
        this(false);
    }
    /**
     * Creates a new {@code ReentrantReadWriteLock} with
     * the given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }


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


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


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


1.2 读锁


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


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


1.3 写锁


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


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


1.4 锁降级


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


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


二. RxCache 中使用读写锁



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


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


RxCache 的 CacheRepository 类实现了缓存操作的类,它使用了

ReentrantReadWriteLock 用于保证缓存在读写时避免出现多线程的并发问题。


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

class CacheRepository {
    private Memory memory;
    private Persistence persistence;
    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    private final Lock readLock = lock.readLock();
    private final Lock writeLock = lock.writeLock();
    ......
}


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

boolean containsKey(String key) {
        readLock.lock();
        try {
            if (Preconditions.isBlank(key)) return false;
            return (memory != null && memory.containsKey(key)) || (persistence != null && persistence.containsKey(key));
        } finally {
            readLock.unlock();
        }
    }


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

void remove(String key) {
        writeLock.lock();
        try {
            if (Preconditions.isNotBlank(key)) {
                if (memory != null) {
                    memory.evict(key);
                }
                if (persistence != null) {
                    persistence.evict(key);
                }
            }
        } finally {
            writeLock.unlock();
        }
    }


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

<T> Record<T> get(String key, Type type, CacheStrategy cacheStrategy) {
        readLock.lock();
        try {
            Record<T> record = null;
            if (Preconditions.isNotBlanks(key, type)) {
                switch (cacheStrategy) {
                    case MEMORY: {
                        if (memory!=null) {
                            record = memory.getIfPresent(key);
                        }
                        break;
                    }
                    case PERSISTENCE: {
                        if (persistence!=null) {
                            record = persistence.retrieve(key, type);
                        }
                        break;
                    }
                    case ALL: {
                        if (memory != null) {
                            record = memory.getIfPresent(key);
                        }
                        if (record == null && persistence != null) {
                            record = persistence.retrieve(key, type);
                            if (memory!=null && record!=null && !record.isExpired()) { // 如果 memory 不为空,record 不为空,并且没有过期
                                readLock.unlock(); // 先释放读锁
                                writeLock.lock();  // 再获取写锁
                                try {
                                    if (record.isNeverExpire()) { // record永不过期的话,直接保存不需要计算ttl
                                        memory.put(record.getKey(),record.getData());
                                    } else {
                                        long ttl = record.getExpireTime()- (System.currentTimeMillis() - record.getCreateTime());
                                        memory.put(record.getKey(),record.getData(), ttl);
                                    }
                                    readLock.lock();    // 写锁在没有释放之前,获得读锁 (锁降级)
                                } finally {
                                    writeLock.unlock(); // 释放写锁
                                }
                            }
                        }
                        break;
                    }
                }
            }
            return record;
        } finally {
            readLock.unlock();
        }
    }


三. 总结



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

相关文章
|
小程序 API
微信小程序如何点击按钮,复制文本到粘贴板
微信小程序如何点击按钮,复制文本到粘贴板
微信小程序如何点击按钮,复制文本到粘贴板
|
10月前
|
API 数据库 Docker
向量搜索升级指南:FAISS 到 Qdrant 迁移方案与代码实现
FAISS 适合实验,但生产环境痛点诸多:无元数据支持、非服务化、难持久化。迁移到 Qdrant 后,实现开箱即用的向量数据库能力,支持混合搜索、过滤、持久化与高效 API,大幅提升系统稳定性与开发效率,真正打通从研究到生产的闭环。
484 6
向量搜索升级指南:FAISS 到 Qdrant 迁移方案与代码实现
|
数据库
mongo占用内存过大解决方案
自己有一个测试用的服务器,配置很低。年前出现几次问题,重启后就好了也就没注意。后来越来越频繁就调查了一下,发现重启后内存就一直增长直到接近100%。使用ps aux查看cpu和内存使用情况,发现mongo占用了大部分的内存,这是什么情况?
1180 0
|
监控 搜索推荐 API
《巧用拼多多 API,精准分析拼多多店铺流量转化密码》
本文详解如何利用拼多多API实现店铺流量转化分析,通过数据驱动优化运营策略。内容涵盖API接入方法、流量与转化率计算模型、优化策略及实战案例,助力商家精准提升销售转化与运营效率。
1257 0
|
9月前
|
数据采集 人工智能 搜索推荐
大模型入门指南:从看懂原理到动手微调,一步步打造你的专属AI
本文深入浅出地讲解大模型核心技术:从Token、Embedding到上下文窗口,揭秘AI如何理解语言;通过提示词工程、RAG与微调,教你打造专属智能助手。零基础也能学会,让AI真正为你所用,开启个性化智能时代。
1662 1
|
10月前
|
自然语言处理 监控 搜索推荐
分布式搜索引擎ElasticSearch
ElasticSearch是基于Lucene的分布式开源搜索引擎,支持海量数据的快速检索、日志分析与实时监控。通过倒排索引实现高效模糊查询,结合Kibana、Logstash等组件构成ELK技术栈,广泛应用于搜索、日志统计与系统监控场景。
364 0
|
Rust 前端开发 JavaScript
低代码使用问题之Supabase是什么,支持哪些客户端库
低代码使用问题之Supabase是什么,支持哪些客户端库
|
Ubuntu Linux Shell
深入理解Linux命令 - autoconf
`autoconf`是Linux软件开发中的自动化配置工具,它根据`configure.ac`模板生成`configure`脚本,检测系统特性并创建适应性的Makefile。工作流程包括读取模板、执行宏、生成配置脚本及运行配置。安装`autoconf`后,编写`configure.ac`,运行`autoconf`生成`configure`,再执行`./configure`以配置项目。此工具简化了跨平台编译的复杂性。
|
机器学习/深度学习 JSON 测试技术
CNN依旧能战:nnU-Net团队新研究揭示医学图像分割的验证误区,设定先进的验证标准与基线模型
在3D医学图像分割领域,尽管出现了多种新架构和方法,但大多未能超越2018年nnU-Net基准。研究发现,许多新方法的优越性未经严格验证,揭示了验证方法的不严谨性。作者通过系统基准测试评估了CNN、Transformer和Mamba等方法,强调了配置和硬件资源的重要性,并更新了nnU-Net基线以适应不同条件。论文呼吁加强科学验证,以确保真实性能提升。通过nnU-Net的变体和新方法的比较,显示经典CNN方法在某些情况下仍优于理论上的先进方法。研究提供了新的标准化基线模型,以促进更严谨的性能评估。
977 0
|
Java 编译器 C++
Protocol Buffers(proto3) 指南
Protocol Buffers(proto3) 指南
454 0

热门文章

最新文章