preface

I believe that the disk cache exists in most apps have applications, compared to the database cache, can not pay attention to the management of the cache, more open and casual. In addition to the Disklrucache framework released by JakeWharton earlier this year, it makes it easier to use disk caching and improves the efficiency of database caching. Later I will add the disklrucache cache interpretation.

However, in the multi-threaded environment, reading and writing the same data will involve the problem of thread safety. For example, when one thread is reading data, another thread is writing data, resulting in data inconsistency. When one thread is writing data, another thread is also writing data, which also causes data inconsistency between the two threads. Even worse, one thread is writing while another thread is reading. The data inconsistency here is for the file. When the data in the file is stored in JSON, the incomplete data or incomplete data cannot generate objects. It is judged that the data is not written well or even the error flash back is reported.

Common Solutions

Use Synchronized lock to protect thread safety, but Synchronized has an obvious performance problem is mutual exclusion between reads, that is, the read operations of two threads are executed in sequence

    public static void main(String[] args) {

        new Thread(new Runnable() {
            @Override
            public void run() {
                read(Thread.currentThread());
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                read(Thread.currentThread());
            }
        }).start();

    }

    public synchronized static void read(Thread thread){
        System.out.println("Start time :"+System.currentTimeMillis());
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("End run time :"+System.currentTimeMillis());
    }

Copy the code

If we look at the result of the run, we can conclude that the reads of two threads are executed sequentially. If there are many reads, this will affect performance too much

thinking

Read and write are mutually exclusive. Write and read are mutually exclusive. ReadWriteLock is a lock that allows many people to read, but not write at the same time

ReadWriteLock introduction

1.1 Position of ReadWriteLock

ReadWriteLock is the location of the Java. Java util. Concurrent. The locks, belong to one of the Java concurrency scheme

1.2 ReadWriteLock is an interface with two main methods, as follows

public interface ReadWriteLock {
    /**
     * Returns the lock used for reading.
     *
     * @return the lock used for reading
     */
    Lock readLock();

    /**
     * Returns the lock used for writing.
     *
     * @return the lock used for writing
     */
    Lock writeLock();
}

Copy the code

Since this is just an interface, we really want to use the class ReentrantReadWriteLock that implements this interface

1.3 reentrant

A reentrant lock allows a thread to acquire the same lock more than once. Generally speaking, it is supported to read and write multiple files in the same thread, and can obtain the same lock, but the number of locks to retrieve the number of locks, the following example is convenient to understand

    public static void main(String[] args) {

        final ReadWriteLock lock = new ReentrantReadWriteLock();

        lock.writeLock().lock();
        lock.writeLock().lock();

        new Thread(new Runnable() {
            @Override
            public void run() {
                lock.writeLock().lock();
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Child thread running");
                lock.writeLock().unlock();
            }
        }).start();

        System.out.println("Main thread running"); lock.writeLock().unlock(); // lock.writeLock().unlock(); Get two locks, release only one lock}Copy the code

The results

1.4 Obtaining lock sequence

  • Unfair mode (default)

    When unfairly initialized, the order in which read and write locks are acquired is uncertain. Unfair locks contend for acquisition and may delay one or more read or write threads, but have higher throughput than fair locks.

  • Fair mode

    When initialized in fair mode, threads will acquire locks in queue order. When the current thread releases the lock, the thread that waits the longest is assigned the write lock. Or if there is a group of reader thread groups that wait longer than the writer thread, the read lock will be assigned to that group.

  • The source code is as follows

    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);
    }

Copy the code

1.5 Lock Upgrade and Lock degradation

  • Lock degradation: from write lock to read lock;
  • Lock upgrade: Change from read lock to write lock.
  • ReentrantReadWriteLock supports only lock degradation
  • It is recommended not to use the lock degradation operation as far as possible. The lock that is acquired should be recycled. The same thread should not use two kinds of locks as far as possible

2 Optimal design of disk cache

Provide abstract class BaseCache source code, concrete implementation we can expand through their own actual situation

public abstract class BaseCache { private final ReadWriteLock mLock = new ReentrantReadWriteLock(); /** * read cache ** @param key Cache key * @param existTime Cache time */ final <T> T load(Type)type, String key, long existTime) { //1. Check key utils. checkNotNull(key,"key == null"); //2. Check whether the key exists. If the key does not exist, it is meaningless to read cacheif(! containsKey(key)) {returnnull; } //3. Check whether the expiration date is expiredif (isExpiry(key, existTime)) {
            remove(key);
            returnnull; } //4. Start the actual read cache mlock. readLock().lock(); Try {// reads the cachereturn doLoad(type, key); } finally { mLock.readLock().unlock(); }} /** * save cache ** @param key Cache key * @param value Cache contents * @return*/ final <T> boolean save(String key, T value) { //1. Check key utils. checkNotNull(key,"key == null"); //2. If the value to be saved is empty, delete itif (value == null) {
            returnremove(key); } //3. Write cache Boolean status =false;
        mLock.writeLock().lock();
        try {
            status = doSave(key, value);
        } finally {
            mLock.writeLock().unlock();
        }
        returnstatus; } /** * delete cache */ final Boolean remove(String key) {mlock. writeLock().lock(); try {return doRemove(key); } finally { mLock.writeLock().unlock(); }} /** * get the cache size * @return
     */
    long size() {
        returngetSize(); } /** * Clear the cache */ final Booleanclear() {
        mLock.writeLock().lock();
        try {
            return doClear(); } finally { mLock.writeLock().unlock(); }} /** * final is included so that subclasses cannot be overridden and can only be useddoContainsKey * here is a lock handler, safe operation. <br> * * @param key Cache key * @return*/ public final Boolean containsKey(String key) {mlock. readLock().lock(); try {return doContainsKey(key); } finally { mLock.readLock().unlock(); }} /** * Whether to include protected modifiers modified by subclasses */ protected abstract BooleandoContainsKey(String key); /** * Whether expiry */ protected abstract Boolean expiry (String key, long existTime); /** * protected <T> TdoLoad(Type type, String key); / / protected <T> BooleandoSave(String key, T value); /** * delete cache */ protected BooleandoRemove(String key); /** * clear the cache */ protected BooleandoClear(); /** * get the cache size ** @return
     */
    protected abstract long getSize();
}

Copy the code