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

cool girl.jpg

一. ReentrantReadWriteLock读写锁

Lock 是至关于 synchronized 更面向对象的同步方式,ReentrantLock 是 Lock 的实现。java

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

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

/** 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 。post

1.3 写锁

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

写锁能够降级成读锁,下面会介绍锁降级。

1.4 锁降级

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

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

二. RxCache 中使用读写锁

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

github地址:github.com/fengzhizi71…

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 使用读写锁来实现缓存的操做。

RxCache 系列的相关文章:

  1. 堆外内存及其在 RxCache 中的使用
  2. Retrofit 风格的 RxCache及其多种缓存替换算法
  3. RxCache 整合 Android 的持久层框架 greenDAO、Room
  4. 给 Java 和 Android 构建一个简单的响应式Local Cache

Java与Android技术栈:每周更新推送原创技术文章,欢迎扫描下方的公众号二维码并关注,期待与您的共同成长和进步。

相关文章
相关标签/搜索