美文网首页多线程专家
ReentrantReadWriteLock读写锁及其在 RxC

ReentrantReadWriteLock读写锁及其在 RxC

作者: fengzhizi715 | 来源:发表于2019-01-21 01:02 被阅读125次
    cool girl.jpg

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

    RxCache 系列的相关文章:

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

    相关文章

      网友评论

        本文标题:ReentrantReadWriteLock读写锁及其在 RxC

        本文链接:https://www.haomeiwen.com/subject/suhzdqtx.html