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類的關鍵一環,其中二級緩存的設計思想很有研究的價值,特別是一些內部類的設計與使用,都可以給我們之後的編碼帶來啓發。