JDK動態代理(3)WeakCache快取的實現機制
上一篇我們分析了Proxy類的內部是怎樣產生代理類的,我們看到了Proxy內部用到了快取機制,如果根據提供的類載入器和介面陣列能在快取中找到代理類就直接返回該代理類,否則會呼叫ProxyClassFactory工廠去生成代理類。這裡用到的快取是二級快取,它的一級快取key是根據類載入器生成的,二級快取key是根據介面陣列生成的。具體的內部機制我們直接貼上程式碼詳細解釋。
//Reference引用佇列
private final ReferenceQueue<K> refQueue = new ReferenceQueue<>();
//快取的底層實現, key為一級快取, value為二級快取。 為了支援null, map的key型別設定為Object
private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>>
map = new ConcurrentHashMap<>();
//reverseMap記錄了所有代理類生成器是否可用, 這是為了實現快取的過期機制
private final ConcurrentMap<Supplier<V>, Boolean> reverseMap = new ConcurrentHashMap<>();
//生成二級快取key的工廠, 這裡傳入的是KeyFactory
private final BiFunction<K, P, ?> subKeyFactory;
//生成二級快取value的工廠, 這裡傳入的是ProxyClassFactory
private final BiFunction<K, P, V> valueFactory;
//構造器, 傳入生成二級快取key的工廠和生成二級快取value的工廠
public WeakCache(BiFunction<K, P, ?> subKeyFactory, BiFunction<K, P, V> valueFactory) {
this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
this.valueFactory = Objects.requireNonNull(valueFactory);
}
首先我們看一下WeakCache的成員變數和構造器,WeakCache快取的內部實現是通過ConcurrentMap來完成的,成員變數map就是二級快取的底層實現,reverseMap是為了實現快取的過期機制,subKeyFactory是二級快取key的生成工廠,通過構造器傳入,這裡傳入的值是Proxy類的KeyFactory,valueFactory是二級快取value的生成工廠,通過構造器傳入,這裡傳入的是Proxy類的ProxyClassFactory。接下來我們看一下WeakCache的get方法。
public V get(K key, P parameter) {
//這裡要求實現的介面不能為空
Objects.requireNonNull(parameter);
//清除過期的快取
expungeStaleEntries();
//將ClassLoader包裝成CacheKey, 作為一級快取的key
Object cacheKey = CacheKey.valueOf(key, refQueue);
//獲取得到二級快取
ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
//如果根據ClassLoader沒有獲取到對應的值
if (valuesMap == null) {
//以CAS方式放入, 如果不存在則放入,否則返回原先的值
ConcurrentMap<Object, Supplier<V>> oldValuesMap = map.putIfAbsent(cacheKey,
valuesMap = new ConcurrentHashMap<>());
//如果oldValuesMap有值, 說明放入失敗
if (oldValuesMap != null) {
valuesMap = oldValuesMap;
}
}
//根據代理類實現的介面陣列來生成二級快取key, 分為key0, key1, key2, keyx
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
//這裡通過subKey獲取到二級快取的值
Supplier<V> supplier = valuesMap.get(subKey);
Factory factory = null;
//這個迴圈提供了輪詢機制, 如果條件為假就繼續重試直到條件為真為止
while (true) {
//如果通過subKey取出來的值不為空
if (supplier != null) {
//在這裡supplier可能是一個Factory也可能會是一個CacheValue
//在這裡不作判斷, 而是在Supplier實現類的get方法裡面進行驗證
V value = supplier.get();
if (value != null) {
return value;
}
}
if (factory == null) {
//新建一個Factory例項作為subKey對應的值
factory = new Factory(key, parameter, subKey, valuesMap);
}
if (supplier == null) {
//到這裡表明subKey沒有對應的值, 就將factory作為subKey的值放入
supplier = valuesMap.putIfAbsent(subKey, factory);
if (supplier == null) {
//到這裡表明成功將factory放入快取
supplier = factory;
}
//否則, 可能期間有其他執行緒修改了值, 那麼就不再繼續給subKey賦值, 而是取出來直接用
} else {
//期間可能其他執行緒修改了值, 那麼就將原先的值替換
if (valuesMap.replace(subKey, supplier, factory)) {
//成功將factory替換成新的值
supplier = factory;
} else {
//替換失敗, 繼續使用原先的值
supplier = valuesMap.get(subKey);
}
}
}
}
WeakCache的get方法並沒有用鎖進行同步,那它是怎樣實現執行緒安全的呢?因為它的所有會進行修改的成員變數都使用了ConcurrentMap,這個類是執行緒安全的。因此它將自身的執行緒安全委託給了ConcurrentMap, get方法儘可能的將同步程式碼塊縮小,這樣可以有效提高WeakCache的效能。我們看到ClassLoader作為了一級快取的key,這樣可以首先根據ClassLoader篩選一遍,因為不同ClassLoader載入的類是不同的。然後它用介面陣列來生成二級快取的key,這裡它進行了一些優化,因為大部分類都是實現了一個或兩個介面,所以二級快取key分為key0,key1,key2,keyX。key0到key2分別表示實現了0到2個介面,keyX表示實現了3個或以上的介面,事實上大部分都只會用到key1和key2。這些key的生成工廠是在Proxy類中,通過WeakCache的構造器將key工廠傳入。這裡的二級快取的值是一個Factory例項,最終代理類的值是通過Factory這個工廠來獲得的。
private final class Factory implements Supplier<V> {
//一級快取key, 根據ClassLoader生成
private final K key;
//代理類實現的介面陣列
private final P parameter;
//二級快取key, 根據介面陣列生成
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;
}
@Override
public synchronized V get() {
//這裡再一次去二級快取裡面獲取Supplier, 用來驗證是否是Factory本身
Supplier<V> supplier = valuesMap.get(subKey);
if (supplier != this) {
//在這裡驗證supplier是否是Factory例項本身, 如果不則返回null讓呼叫者繼續輪詢重試
//期間supplier可能替換成了CacheValue, 或者由於生成代理類失敗被從二級快取中移除了
return null;
}
V value = null;
try {
//委託valueFactory去生成代理類, 這裡會通過傳入的ProxyClassFactory去生成代理類
value = Objects.requireNonNull(valueFactory.apply(key, parameter));
} finally {
//如果生成代理類失敗, 就將這個二級快取刪除
if (value == null) {
valuesMap.remove(subKey, this);
}
}
//只有value的值不為空才能到達這裡
assert value != null;
//使用弱引用包裝生成的代理類
CacheValue<V> cacheValue = new CacheValue<>(value);
//將包裝後的cacheValue放入二級快取中, 這個操作必須成功, 否則就報錯
if (valuesMap.replace(subKey, this, cacheValue)) {
//將cacheValue成功放入二級快取後, 再對它進行標記
reverseMap.put(cacheValue, Boolean.TRUE);
} else {
throw new AssertionError("Should not reach here");
}
//最後返回沒有被弱引用包裝的代理類
return value;
}
}
我們再看看Factory這個內部工廠類,可以看到它的get方法是使用synchronized關鍵字進行了同步。進行get方法後首先會去驗證subKey對應的suppiler是否是工廠本身,如果不是就返回null,而WeakCache的get方法會繼續進行重試。如果確實是工廠本身,那麼就會委託ProxyClassFactory生成代理類,ProxyClassFactory是在構造WeakCache的時候傳入的。所以這裡解釋了為什麼最後會呼叫到Proxy的ProxyClassFactory這個內部工廠來生成代理類。生成代理類後使用弱引用進行包裝並放入reverseMap中,最後會返回原裝的代理類。
至此已經為大家詳細揭示了WeakCache快取的實現包括它的一級快取和二級快取實現的原理,以及二級快取key生成的原理,還有最後它是怎樣呼叫ProxyClassFactory來生成代理類的。在下一篇中將會深入ProxyGenerator這個類,來看看具體的代理類的位元組碼生成過程。