Android読み書きロックの応用、および最適なディスクキャッシュ設計

9997 ワード

前言
ディスクキャッシュはほとんどのappに応用されていると信じられています.データベースキャッシュに比べて、キャッシュの管理を重視せず、開放的で自由にすることができます.さらにjakewhartonが昨年発表したdisklrucacheフレームワークを加えると、ディスクキャッシュを使用するのが簡単になり、効率的にデータベースキャッシュも一歩進み、後でdisklrucacheのキャッシュ解読を加える時間があります.
しかし、マルチスレッド環境では、同じデータを読み書きすることは、スレッドのセキュリティの問題に関連します.例えば、あるスレッドがデータを読み出すとき、別のスレッドがデータを書いているため、前後のデータの不一致を招く.1つのスレッドがデータを書くとき、もう1つのスレッドも書いているので、同じようにスレッドの前後に見られるデータの不一致を招きます.さらに深刻なのは、1つのスレッドが書かれている間に、もう1つのスレッドが読んでいることです.ここでのデータの不一致はファイルにとって,ファイル内のデータが格納されているjsonの場合,欠落したデータや不完全なデータがオブジェクトを生成できず,書き上げられていないと判断したり,エラーフラッシュバックを報告したりする.
一般的なソリューション
Synchronized同期ロックを使用してスレッドのセキュリティを保護しますが、Synchronizedのパフォーマンスには、読み取りと読み取りの間の反発が明らかになっています.つまり、2つのスレッドの読み取り操作は順番に実行されます.次に、コードを見て理解しやすいようにします.
    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("      :"+System.currentTimeMillis());
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("      :"+System.currentTimeMillis());
    }


実行結果を見てみると,2つの2つのスレッドの読み取り操作は順次実行され,読み取り回数が多ければ性能に影響を及ぼすと結論した.
考える
最も良い案は通俗的に言えば、多くの人が同時に読むことができますが、同時に書くことはできません.書くときに同時に読むことも書くこともできません.公式には、読むことと読むことは互いに影響しません.読むことと書くことは反発し、書くことと書くことは反発します.では、今日の主役ReadWriteLockの読み書きロックを紹介します.
ReadWriteLockの紹介
1.1 ReadWriteLockの位置
ReadWriteLockはJavaが持参した場所javaである.util.concurrent.locksはjavaコンカレントスキームの1つに属する
1.2 ReadWriteLockは1つのインタフェースで、主に2つの方法があり、以下の通りである.
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();
}


インタフェースだけである以上、インタフェースを実装するクラスReentrantReadWriteLockが読み書きロックに再アクセスできるようにする必要があります.
1.3再入力可能
再ロック可能とは、1つのスレッドがロックを取得した後もロックを取得し続けることです.つまり、1つのスレッドが同じロックを複数回取得できるようにします.一般的には、同じスレッド内で複数のファイルを読み書きする操作をサポートし、同じロックを取得することができますが、どのロックを取得するかはどのロックを回収する必要があります.以下に例をあげて理解しやすいです.
    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("     ");
                lock.writeLock().unlock();
            }
        }).start();

        System.out.println("     ");
        lock.writeLock().unlock();
//        lock.writeLock().unlock();      ,      
        
    }


実行結果注意:プライマリ・スレッドは2回ロックを取得したが、1回のロックのみが解放され、デッドロックが発生し、新しいスレッドは永遠にロックを取得できない.1つのスレッドが何回ロックを取得するかは、何回ロックを解放する必要があります.
1.4ロック順序の取得
  • 非公平モード(デフォルト)非公平で初期化された場合、リードロックおよびライトロックの取得順序は不確定である.非フェアロックは、競合取得を主張し、1つ以上のリードまたはライトスレッドを遅らせる可能性がありますが、フェアロックよりもスループットが高くなります.
  • フェアモードフェアモードで初期化すると、スレッドはキューの順序でロックを取得します.現在のスレッドがロックを解除すると、最も待ち時間の長い書き込みロックスレッドに書き込みロックが割り当てられます.または、書き込みスレッドよりもリードスレッドグループの待機時間が長い場合、リードスレッドグループにはリードロックが割り当てられます.
  • ソースコードは次の
  • です.
        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);
        }
    
    

    1.5ロックアップグレードとロックダウングレード
  • ロックダウングレード:書き込みロックから読み取りロックに変更;
  • ロックアップグレード:リードロックからライトロックに変更します.
  • ReentrantReadWriteLockはロックダウングレード
  • のみをサポート
  • できるだけロックダウングレード操作を使用しないことを提案し、何かロックを取得すると何かロックを回収し、同じスレッドはできるだけ2つのロックを使用しないでください.最も安全で、特別な操作がない限り
  • に注意しなければなりません.
    2ディスクキャッシュの最適設計
    抽象クラスBaseCacheのソースコードを提供し、具体的には自分の実際の状況を通じて拡張することができます.
     public abstract class BaseCache {
    
        private final ReadWriteLock mLock = new ReentrantReadWriteLock();
    
        /**
         *     
         *
         * @param key         key
         * @param existTime     
         */
        final  T load(Type type, String key, long existTime) {
            //1.   key
            Utils.checkNotNull(key, "key == null");
    
            //2.  key    ,key          
            if (!containsKey(key)) {
                return null;
            }
    
            //3.      ,      
            if (isExpiry(key, existTime)) {
                remove(key);
                return null;
            }
    
            //4.         
            mLock.readLock().lock();
            try {
                //     
                return doLoad(type, key);
            } finally {
                mLock.readLock().unlock();
            }
        }
    
        /**
         *     
         *
         * @param key     key
         * @param value     
         * @return
         */
        final  boolean save(String key, T value) {
            //1.   key
            Utils.checkNotNull(key, "key == null");
    
            //2.         ,   
            if (value == null) {
                return remove(key);
            }
    
            //3.    
            boolean status = false;
            mLock.writeLock().lock();
            try {
                status = doSave(key, value);
            } finally {
                mLock.writeLock().unlock();
            }
            return status;
        }
    
        /**
         *     
         */
        final boolean remove(String key) {
            mLock.writeLock().lock();
            try {
                return doRemove(key);
            } finally {
                mLock.writeLock().unlock();
            }
        }
    
    
        /**
         *       
         * @return
         */
        long size() {
            return getSize();
        }
    
        /**
         *     
         */
        final boolean clear() {
            mLock.writeLock().lock();
            try {
                return doClear();
            } finally {
                mLock.writeLock().unlock();
            }
        }
    
        /**
         *       final          ,    doContainsKey
         *        ,    。
    * * @param key key * @return */ public final boolean containsKey(String key) { mLock.readLock().lock(); try { return doContainsKey(key); } finally { mLock.readLock().unlock(); } } /** * protected */ protected abstract boolean doContainsKey(String key); /** * */ protected abstract boolean isExpiry(String key, long existTime); /** * */ protected abstract T doLoad(Type type, String key); /** * */ protected abstract boolean doSave(String key, T value); /** * */ protected abstract boolean doRemove(String key); /** * */ protected abstract boolean doClear(); /** * * * @return */ protected abstract long getSize(); }