Java源碼閱讀------WeakCache

描述

根據名字就知道這是一個緩存類,具體點是一個鍵值對存儲的緩存類,至於weak的含義是因爲這裏的鍵與值都是弱引用,除此之外,這裏所說的緩存是一個二級緩存。第一層是弱引用,第二層是強引用。

實現

二級緩存

private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map = new ConcurrentHashMap<>();
private final ConcurrentMap<Supplier<V>, Boolean> reverseMap = new ConcurrentHashMap<>();

這裏的map就是一個二級緩存的實現機制,Object(最左邊的)就是key(鍵),第二個Object爲subKey(子鍵),最後的Supplier< V >接口爲值,實際是一個包裹最終是由其中的get方法來獲取值。這種設計保證了key爲null時也可用。reverseMap用於標註每個Supplier< V >的可用情況,用於緩存的過期處理。

構造方法

這裏的K,P,V分別是鍵、參數、值,傳入的參數是兩個BiFunction接口,主要使用的是其中的apply方法,定義如下:

R apply(T t, U u);

通過傳入的兩個參數來獲取值,使用這一接口實現了兩個簡單的工廠,subKeyFactory,valueFactory,這兩個工廠分別實現了通過K與P來獲取subKey(子鍵)和通過K,P來獲取value值。

public WeakCache(BiFunction<K, P, ?> subKeyFactory, BiFunction<K, P, V> valueFactory) {
	this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
	this.valueFactory = Objects.requireNonNull(valueFactory);
}

這樣我們就可以通過subKeyFactory ,valueFactory 獲取對應的子鍵與值。

CacheValue

靜態內部類,實際上就是用於存儲一個值的對象。
先看看WeakCache中的Value接口繼承自Supplier接口,實際上是爲了實現get方法

private interface Value<V> extends Supplier<V> {}

@FunctionalInterface
public interface Supplier<T> {
    T get();
}

構造方法

private final int hash;
CacheValue(V value) {
	super(value);
    this.hash = System.identityHashCode(value); // compare by identity
}

實現了傳入的Value的實例保存,同時通過System.identityHashCode(value);函數獲取了該Value的HashCode。

hashCode與identityHashCode

在Object類中的hashCode可以獲取相應對象的hashCode,而這個identityHashCode也是可以獲取對象的hashCode,那麼兩這有什麼不同嗎?從源碼看兩者都是本地方法(native),實際上獲取時的結果是與hashCode無異的,但是這裏的hashCode指的是原有的Object中的hashCode的方法,如果進行了重寫就可能會有不同了,所以爲了得到原有的Object中的hashCode的值,identityHashCode會比較方便。

hashCode

重寫的hashCode方法,直接返回hash值。

@Override
public int hashCode() {
	return hash;
}

equals

重寫的equals方法,用於判別傳入的obj與弱引用中的value是否相同,這裏的get()方法就是返回之前傳入的弱引用的value。

@Override
public boolean equals(Object obj) {
	V value;
    return obj == this || obj instanceof Value &&
            (value = get()) != null &&  value == ((Value<?>) obj).get(); 
}

LookupValue

靜態內部類,爲了便於對CacheValue中的值進行判斷,建立了LookupValue,也實現了Value接口,是CacheValue運算時的替代,實現方式也很相似。

		private final V value;//存儲實際的值

        LookupValue(V value) {//構造方法
            this.value = value;
        }

        @Override
        public V get() {//Value接口中的get方法,返回value的值
            return value;
        }

        @Override
        public int hashCode() {
            return System.identityHashCode(value); //一樣的hashCode計算
        }

        @Override
        public boolean equals(Object obj) {
            return obj == this ||
                   obj instanceof Value &&
                   this.value == ((Value<?>) obj).get();  // 類似的equals重寫
        }

CacheKey

靜態內部類,CacheKey直接繼承了使用引用隊列的弱引用,來存儲鍵值。

構造方法

實現的過程與CacheValue中的類似,只不過這裏使用引用隊列。

private final int hash;

private CacheKey(K key, ReferenceQueue<K> refQueue) {
	super(key, refQueue);
    this.hash = System.identityHashCode(key); 
}

但是這裏有一點注意,這裏的構造方法是private的也就是說無法從外界直接調用,那麼是如何構建出實例對象的呢?

valueOf

這是一個靜態方法,傳入的是要保存的key與對應的請求隊列。

static <K> Object valueOf(K key, ReferenceQueue<K> refQueue) {
	return key == null ? NULL_KEY : new CacheKey<>(key, refQueue);
}

這裏的NULL_KEY是一個標識用於標註key爲空的情況。

private static final Object NULL_KEY = new Object();

hashCode與equals

這裏重寫了hashCode與equals方法,基本的實現與CacheValue相同,不再贅述。

@Override
public int hashCode() {
	return hash;
}

@Override
public boolean equals(Object obj) {
	K key;
    return obj == this || obj != null &&
           obj.getClass() == this.getClass() && (key = this.get()) != null &&
           key == ((CacheKey<K>) obj).get();
}

這裏的get方法是引用Reference中定義的與之後描述的get方法不同。

expungeFrom

通過這個方法可以將含有這個鍵值的相關緩存清除。

void expungeFrom(ConcurrentMap<?, ? extends ConcurrentMap<?, ?>> map, ConcurrentMap<?, Boolean> reverseMap) {
	ConcurrentMap<?, ?> valuesMap = map.remove(this);//直接從二級緩存中清除,並獲取第二級的緩存map
    if (valuesMap != null) {//遍歷第二級緩存並在reverseMap中清除
    	for (Object cacheValue : valuesMap.values()) {
        	reverseMap.remove(cacheValue);
        }
    }
}

Factory

二級緩存的構建過程是通過這個靜態內部類實現的,實現了Supplier接口。

構造方法

		private final K key;//鍵
        private final P parameter;//參數
        private final Object subKey;//子鍵
        private final ConcurrentMap<Object, Supplier<V>> valuesMap;//二級緩存

        Factory(K key, P parameter, Object subKey, ConcurrentMap<Object, Supplier<V>> valuesMap) {
            this.key = key;
            this.parameter = parameter;
            this.subKey = subKey;
            this.valuesMap = valuesMap;
        }

簡單的數據準備工作。

get

這就是構建的具體過程了,實現了Supplier中的get方法,通過同步synchronized來保持線程安全

		@Override
        public synchronized V get() { // serialize access
            // 這裏有一個檢查,爲啥檢查先買個關子,到之後WeakCatch的get方法再做細究
            Supplier<V> supplier = valuesMap.get(subKey);
            if (supplier != this) {
                return null;
            }
            V value = null;
            try {
                value = Objects.requireNonNull(valueFactory.apply(key, parameter));
                //通過key與parameter處理出value
            } finally {
                if (value == null) { // 失敗的處理
                    valuesMap.remove(subKey, this);//移除相應的subKey對應的緩存
                }
            }
            // 檢查value是否完成建立
            assert value != null;

            // 包裹一下value建立一個cacheValue
            CacheValue<V> cacheValue = new CacheValue<>(value);
            // 將subKey中的二級緩存中原本的Factory換成包裝過的cacheValue,至於爲啥一開始二級緩存中會將Factory放進去我們也將其放到WeakCatch的get方法中解釋
            if (valuesMap.replace(subKey, this, cacheValue)) {
                // 加入reverseMap中標記Value可用
                reverseMap.put(cacheValue, Boolean.TRUE);
            } else {//出錯時的異常處理
                throw new AssertionError("Should not reach here");
            }
            //處理成功後將value返回
            return value;
        }

expungeStaleEntries

由於二級緩存中的Key使用了弱引用,所以在實際使用時gc的不定期處理會導致部分的緩存失效,通過這個函數就可以實現對失效緩存的清除。

	private final ReferenceQueue<K> refQueue = new ReferenceQueue<>();
	private void expungeStaleEntries() {
        CacheKey<K> cacheKey;
        while ((cacheKey = (CacheKey<K>)refQueue.poll()) != null) {
            cacheKey.expungeFrom(map, reverseMap);
        }
    }

refQueue是弱引用中的引用隊列,在創建CacheKey時傳入。(建議瞭解弱引用後繼續)。
當gc處理了部分CacheKey時,refQueue中會有CacheKey的引用,取出來後在調用expungeFrom方法來清除過期的緩存。

size

同樣的在獲取可用的緩存數量時也使用了expungeStaleEntries來先清除過期的緩存。

	public int size() {
        expungeStaleEntries();
        return reverseMap.size();
    }

containsValue

判斷緩存中是否包含對應的value。

	public boolean containsValue(V value) {
        Objects.requireNonNull(value);//對值進行判空
        expungeStaleEntries();//清除過期緩存
        return reverseMap.containsKey(new LookupValue<>(value));//使用LookupValue代替CacheValue使用,簡化操作。
    }

get

壓軸的高潮來了,這是整個類的靈魂所在,緩存中Value的值是由這個函數來獲取的。

	public V get(K key, P parameter) {
		//參數判空
        Objects.requireNonNull(parameter);
		//清除過期緩存
        expungeStaleEntries();
		//根據key生成相應的cacheKey 
        Object cacheKey = CacheKey.valueOf(key, refQueue);

        // 獲取二級緩存
        ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
        if (valuesMap == null) {//如果沒有對應的二級緩存
            ConcurrentMap<Object, Supplier<V>> oldValuesMap = map.putIfAbsent(cacheKey, valuesMap = new ConcurrentHashMap<>());//這裏的putIfAbsent與put不同,put會直接替代,putIfAbsent是先判斷是否含有值,如果有就返回對應值,如果沒有就放入新值並返回null
            //如果之前含有值就使用之前的。
            if (oldValuesMap != null) {
                valuesMap = oldValuesMap;
            }
        }
        // 通過subKeyFactory的apply方法創建subKey
        Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
        // 通過subKey獲取相應的值
        Supplier<V> supplier = valuesMap.get(subKey);
        Factory factory = null;

        while (true) {
        	//緩存中有值且不爲null就用get方法取出判空返回
            if (supplier != null) {
                // 根據之前的Factory中的操作,我們知道這裏的supplier可能是Factory或CacheValue<V>類型
                V value = supplier.get();
                if (value != null) {
                    return value;
                }
            }
            //緩存沒有成功取到或是沒有緩存時,使用Factory進行加載。
            if (factory == null) {
            	//創建factory
                factory = new Factory(key, parameter, subKey, valuesMap);
            }
			//不含緩存或取值爲空的處理
            if (supplier == null) {
                supplier = valuesMap.putIfAbsent(subKey, factory);
                if (supplier == null) {
                    //緩存加入成功,將最終的返回值設爲factory在下一次循環時返回
                    supplier = factory;
                }
            } else {//之前含有緩存
                if (valuesMap.replace(subKey, supplier, factory)) {//用新的factory進行替換
                    //成功替換後就將返回值設爲factory在下一次循環時返回
                    supplier = factory;
                } else {//替換失敗則將其原來的值取出
                    supplier = valuesMap.get(subKey);
                }
            }
        }
    }

說到這裏還有一個問題沒有解決就是循環輪詢的目的是啥,由於在實際使用時可能有多個線程對緩存中的值進行操作,所以使用輪詢來不斷的進行判斷以獲取最新的值,這裏的get方法可以與factory中的get方法比較來看,就很好理解爲啥在factory的get方法中要對取到的supplier進行判斷了。

小結

WeakCache是實現Proxy類的關鍵一環,其中二級緩存的設計思想很有研究的價值,特別是一些內部類的設計與使用,都可以給我們之後的編碼帶來啓發。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章