Android讀寫鎖的應(yīng)用,以及最佳的磁盤緩存設(shè)計(jì)

前言

相信磁盤緩存在絕大部分的app上都有應(yīng)用,相對(duì)于數(shù)據(jù)庫(kù)緩存來說,可以不要注重于緩存的管理,比較開放和隨意。
再加上jakewharton早年間發(fā)布的disklrucache框架,讓我們使用磁盤緩存更加簡(jiǎn)單,效率上和數(shù)據(jù)庫(kù)緩存也拉進(jìn)了一步,以后有時(shí)間我在加上disklrucache的緩存解讀。

但是在多線程的環(huán)境下,對(duì)同一份數(shù)據(jù)進(jìn)行讀寫,會(huì)涉及到線程安全的問題。比如在一個(gè)線程讀取數(shù)據(jù)的時(shí)候,另外一個(gè)線程在寫數(shù)據(jù),而導(dǎo)致前后數(shù)據(jù)的不一致性;一個(gè)線程在寫數(shù)據(jù)的時(shí)候,另一個(gè)線程也在寫,同樣也會(huì)導(dǎo)致線程前后看到的數(shù)據(jù)的不一致性。更嚴(yán)重的是一個(gè)線程在寫的時(shí)候,另一個(gè)線程在讀。這里的數(shù)據(jù)不一致是對(duì)于文件來說的,當(dāng)文件里的數(shù)據(jù)存儲(chǔ)的json時(shí),殘缺的數(shù)據(jù)或者不完整的數(shù)據(jù)無法生成對(duì)象,判斷沒有寫好甚至是報(bào)錯(cuò)閃退。

常見解決方案

使用Synchronized同步鎖保護(hù)線程安全,但是Synchronized存在明顯的一個(gè)性能問題就是讀與讀之間互斥,也就是說兩個(gè)線程的讀操作是順序執(zhí)行的 下面給大家看下代碼方便理解

    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("開始運(yùn)行時(shí)間:"+System.currentTimeMillis());
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("結(jié)束運(yùn)行時(shí)間:"+System.currentTimeMillis());
    }

我們來看一下運(yùn)行結(jié)果,結(jié)論兩個(gè)兩個(gè)線程的讀操作是順序執(zhí)行的,如果讀的次數(shù)多這個(gè)太影響性能了


image

思考

最佳的方案通俗的來講應(yīng)該是,可以很多人同時(shí)讀,但不能同時(shí)寫,有人在寫的時(shí)候不能同時(shí)讀也不能同時(shí)寫,官方說法是讀和讀互不影響,讀和寫互斥,寫和寫互斥,好了接下來就是介紹今天的主角ReadWriteLock 讀寫鎖

ReadWriteLock介紹

1.1 ReadWriteLock的位置

ReadWriteLock是Java自帶的 所處位置 java.util.concurrent.locks,屬于java并發(fā)方案中的一種

1.2 ReadWriteLock是一個(gè)接口,主要有兩個(gè)方法,如下

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

既然只是接口,那我們真正要用的是實(shí)現(xiàn)了該接口的類 ReentrantReadWriteLock 可重入讀寫鎖

1.3可重人

可重入鎖,就是說一個(gè)線程在獲取某個(gè)鎖后,還可以繼續(xù)獲取該鎖,即允許一個(gè)線程多次獲取同一個(gè)鎖。通俗的來講就是支持在同一個(gè)線程里面對(duì)多個(gè)文件進(jìn)行讀寫操作,都可以獲取同一個(gè)鎖,但是獲取多少鎖就要回收多少鎖,下面給個(gè)例子方便理解

    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("子線程運(yùn)行");
                lock.writeLock().unlock();
            }
        }).start();

        System.out.println("主線程運(yùn)行");
        lock.writeLock().unlock();
//        lock.writeLock().unlock(); 獲取兩次鎖,只釋放一次鎖
        
    }

運(yùn)行結(jié)果


image

注意:因?yàn)橹骶€程2次獲取了鎖,但是卻只釋放1次鎖,造成死鎖,導(dǎo)致新線程永遠(yuǎn)也不能獲取鎖。一個(gè)線程獲取多少次鎖,就必須釋放多少次鎖

1.4 獲取鎖順序

  • 非公平模式(默認(rèn))

    當(dāng)以非公平初始化時(shí),讀鎖和寫鎖的獲取的順序是不確定的。非公平鎖主張競(jìng)爭(zhēng)獲取,可能會(huì)延緩一個(gè)或多個(gè)讀或?qū)懢€程,但是會(huì)比公平鎖有更高的吞吐量。

  • 公平模式

    當(dāng)以公平模式初始化時(shí),線程將會(huì)以隊(duì)列的順序獲取鎖。當(dāng)當(dāng)前線程釋放鎖后,等待時(shí)間最長(zhǎng)的寫鎖線程就會(huì)被分配寫鎖;或者有一組讀線程組等待時(shí)間比寫線程長(zhǎng),那么這組讀線程組將會(huì)被分配讀鎖。

  • 源碼如下

    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 鎖升級(jí)和鎖降級(jí)

  • 鎖降級(jí):從寫鎖變成讀鎖;
  • 鎖升級(jí):從讀鎖變成寫鎖。
  • ReentrantReadWriteLock 只支持鎖降級(jí)
  • 建議盡量不要使用鎖降級(jí)操作,獲取什么鎖就回收什么鎖,同一線程盡量不要使用兩種鎖,最為安全,除非有特殊操作則需注意

2 磁盤緩存最佳設(shè)計(jì)

提供抽象類BaseCache的源碼,具體實(shí)現(xiàn)大家可以通過自己的實(shí)際情況去拓展

 public abstract class BaseCache {

    private final ReadWriteLock mLock = new ReentrantReadWriteLock();

    /**
     * 讀取緩存
     *
     * @param key       緩存key
     * @param existTime 緩存時(shí)間
     */
    final <T> T load(Type type, String key, long existTime) {
        //1.先檢查key
        Utils.checkNotNull(key, "key == null");

        //2.判斷key是否存在,key不存在去讀緩存沒意義
        if (!containsKey(key)) {
            return null;
        }

        //3.判斷是否過期,過期自動(dòng)清理
        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 緩存內(nèi)容
     * @return
     */
    final <T> 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
     * 這里加了鎖處理,操作安全。<br>
     *
     * @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> T doLoad(Type type, String key);

    /**
     * 保存
     */
    protected abstract <T> boolean doSave(String key, T value);

    /**
     * 刪除緩存
     */
    protected abstract boolean doRemove(String key);

    /**
     * 清空緩存
     */
    protected abstract boolean doClear();

    /**
     * 獲取緩存大小
     *
     * @return
     */
    protected abstract long getSize();
}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容