在上篇文章01 初識(shí)緩存-了解緩存中簡單了介紹了下緩存的歷程以及幾種常見的技術(shù)進(jìn)行簡單介紹,本著學(xué)習(xí)的目的本節(jié)針對(duì)GuavaCache進(jìn)行一個(gè)專題介紹,可能理解有限歡迎指正。
一 簡介
Guava Cache,是Google 出品的 Java 核心增強(qiáng)庫的緩存部分,一種非常優(yōu)秀本地緩存解決方案,提供了基于容量,時(shí)間和引用的緩存回收方式。guava cache可以按照多種策略來清理存儲(chǔ)在其中的緩存值且保持很高的并發(fā)讀寫性能?;谌萘康姆绞絻?nèi)部實(shí)現(xiàn)采用LRU算法,基于引用回收很好的利用了Java虛擬機(jī)的垃圾回收機(jī)制。其中的緩存構(gòu)造器CacheBuilder采用構(gòu)建者模式提供了設(shè)置好各種參數(shù)的緩存對(duì)象,緩存核心類LocalCache里面的內(nèi)部類Segment與jdk1.7及以前的ConcurrentHashMap非常相似,都繼承于ReetrantLock,還有六個(gè)隊(duì)列,以實(shí)現(xiàn)豐富的本地緩存方案。
二 GuavaCache使用
2.1 緩存加載器 CacheLoader
CacheLoader是GuavaCache提供的一種加載緩存的機(jī)制,可以通過指定的鍵值進(jìn)行計(jì)算/加載需要進(jìn)行匹配的緩存數(shù)據(jù)。GuavaCache雖然提供了這種方式,但是它并不要求編程者一定需要遵循它這種方案,也可以根據(jù)自定的實(shí)際需求來定制自己加載邏輯。根據(jù)GuavaCache提出的如果想復(fù)寫其緩存的加載方式,但是仍要保留“get-if-absent-compute”語義,GuavaCache在進(jìn)行緩存獲取的時(shí)候提供了一種解決方案:可以在調(diào)用get方法時(shí)傳入一個(gè)Callable實(shí)例,來達(dá)到目的。緩存的對(duì)象可以通過Cache.put直接插入,但是自動(dòng)加載是首選,因?yàn)樽詣?dòng)加載可以更加容易的判斷所有緩存信息的一致性。
Guava Cache針對(duì)開發(fā)者提供了非常友好的編程方式,使用非常簡單:
下面例子分別從不同的角度:
From a CacheLoader
LoadingCache 緩存是通過一個(gè)CacheLoader來構(gòu)建緩存。創(chuàng)建一個(gè)CacheLoader僅需要實(shí)現(xiàn)V load(K key) throws Exception方法即可。通過方法get(K)可以對(duì)LoadingCache進(jìn)行查詢。該方法要不返回已緩存的值,要不通過CacheLoader來自動(dòng)加載相應(yīng)的值到緩存中。
From a Callable
所有類型的Guava Cache,不論是否會(huì)自動(dòng)加載,都支持get(K, Callable(V))方法。當(dāng)給定鍵的緩存值已存在時(shí)則直接返回,否則通過指定的Callable方法進(jìn)行計(jì)算并將值存放到緩存中。直到加載完成時(shí),相應(yīng)的緩存才會(huì)被更改。該方法簡單實(shí)現(xiàn)了"if cached, return; otherwise create, cache and return"("如果有緩存則返回;否則運(yùn)算、緩存、然后返回")語義。
Cache.put
使用cache.put(key, value)方法可以直接向緩存中插入值,這會(huì)直接覆蓋掉給定鍵之前映射的值。使用Cache.asMap()視圖提供的任何方法也能修改緩存。但請(qǐng)注意,asMap視圖的任何方法都不能保證緩存項(xiàng)被原子地加載到緩存中。進(jìn)一步說,asMap視圖的原子運(yùn)算在Guava Cache的原子加載范疇之外,所以相比于Cache.asMap().putIfAbsent(K,V),Cache.get(K, Callable<V>) 應(yīng)該總是優(yōu)先使用。
/**
* @author mzw
* @version V1.0.0
* @description
* @data 2019-05-29 14:53
* @see
**/
public class GuavaCache {
private static final Logger LOGGER = LoggerFactory.getLogger(GuavaCache.class);
public static void main(String[] args) throws ExecutionException {
//構(gòu)建緩存加載器
CacheLoader<String, String> loader = new CacheLoader<String, String>() {
@Override
public String load(String key) {
LOGGER.info(key + " is loaded from a cacheLoader!");
return key;
}
};
//構(gòu)建移除監(jiān)聽器:非必要
RemovalListener<String, String> removalListener =
removal -> LOGGER.info("[" + removal.getKey() + ":" + removal.getValue() + "] is evicted!");
//構(gòu)建緩存
LoadingCache<String, String> cache = CacheBuilder.newBuilder()
.maximumSize(4)
.expireAfterWrite(10, TimeUnit.MINUTES)
.removalListener(removalListener)
.build(loader);
//直接放入數(shù)據(jù)
for (int i = 0; i < 3; i++) {
cache.put("key" + i, "value" + i);
}
//獲取數(shù)據(jù)
LOGGER.info("key2 > " + cache.getIfPresent("key2"));
LOGGER.info("key > " + cache.getIfPresent("key"));
//From a CacheLoade
LOGGER.info("key > " + cache.get("key"));
//From a Callable
LOGGER.info("key > " + cache.get("k1", () -> "dd"));
}
}
輸出結(jié)果
17:36:12.084 [main] INFO com.maozw.quartz.cache.GuavaCache - key2 > value2
17:36:12.086 [main] INFO com.maozw.quartz.cache.GuavaCache - key > null
17:36:12.090 [main] INFO com.maozw.quartz.cache.GuavaCache - key is loaded from a cacheLoader!
17:36:12.092 [main] INFO com.maozw.quartz.cache.GuavaCache - key > key
17:36:12.094 [main] INFO com.maozw.quartz.cache.GuavaCache - [key0:value0] is evicted!
17:36:12.094 [main] INFO com.maozw.quartz.cache.GuavaCache - k1 > dd
2.1 緩存回收
guava中數(shù)據(jù)的移除分為被動(dòng)移除和主動(dòng)移除兩種,
被動(dòng)移除數(shù)據(jù)的方式,簡介中曾說過guava cache可以按照多種策略來清理存儲(chǔ)在其中的緩存值且保持很高的并發(fā)讀寫性能?;谌萘康姆绞絻?nèi)部實(shí)現(xiàn)采用LRU算法,基于引用回收很好的利用了Java虛擬機(jī)的垃圾回收機(jī)制。Guava Cache提供了三種基本的緩存回收方式:
基于緩存容量大小的移除
當(dāng)緩存中的元素?cái)?shù)量超過指定值時(shí),會(huì)把不常用的鍵值對(duì)從cache中移除。
緩存容量大小指的是cache中的條目數(shù);
并不是完全到了緩存容量大小才開始移除不常用的數(shù)據(jù)的,而是接近緩存容量大小的時(shí)候系統(tǒng)就會(huì)開始做移除的動(dòng)作;
如果一個(gè)鍵值對(duì)已經(jīng)從緩存中被移除了,你再次請(qǐng)求訪問的時(shí)候,如果cachebuild是使用cacheloader方式的,那依然還是會(huì)從cacheloader中再取一次值,如果還沒有,就會(huì)拋出異常。
LoadingCache<String, String> cache = CacheBuilder.newBuilder()
.maximumSize(4)//緩存容量大小
.expireAfterWrite(10, TimeUnit.MINUTES)
.removalListener(removalListener)
.build(loader);
基于緩存時(shí)間的移除
guava提供了兩個(gè)基于時(shí)間移除的方法
expireAfterAccess(long, TimeUnit):這個(gè)方法是根據(jù)某個(gè)鍵值對(duì)最后一次訪問之后多少時(shí)間后移除。緩存項(xiàng)在給定時(shí)間內(nèi)沒有被讀/寫訪問,則回收。請(qǐng)注意這種緩存的回收順序和基于大小回收一樣。
expireAfterWrite(long, TimeUnit):這個(gè)方法是根據(jù)某個(gè)鍵值對(duì)被創(chuàng)建或值被替換后多少時(shí)間移除。緩存項(xiàng)在給定時(shí)間內(nèi)沒有被寫訪問(創(chuàng)建或覆蓋),則回收。如果認(rèn)為緩存數(shù)據(jù)總是在固定時(shí)候后變得陳舊不可用,這種回收方式是可取的.
LoadingCache<String, String> cache = CacheBuilder.newBuilder()
.maximumSize(4)//緩存容量大小
.expireAfterWrite(10, TimeUnit.MINUTES)//緩存時(shí)間
.removalListener(removalListener)
.build(loader);
基于引用回收(Reference-based Eviction)
這種移除方式主要是基于java的垃圾回收機(jī)制,根據(jù)鍵或者值的引用關(guān)系決定移除
CacheBuilder.weakKeys():使用弱引用存儲(chǔ)鍵。當(dāng)鍵沒有其它(強(qiáng)或軟)引用時(shí),緩存項(xiàng)可以被垃圾回收。
CacheBuilder.weakValues():使用弱引用存儲(chǔ)值。當(dāng)值沒有其它(強(qiáng)或軟)引用時(shí),緩存項(xiàng)可以被垃圾回收。
CacheBuilder.softValues():使用軟引用存儲(chǔ)值。軟引用只有在響應(yīng)內(nèi)存需要時(shí),才按照全局最近最少使用的順序回收。
主動(dòng)移除數(shù)據(jù)方式
主動(dòng)移除有三種方法:
單獨(dú)緩存項(xiàng)移除 Cache.invalidate(key)
批量緩存項(xiàng)移除 Cache.invalidateAll(keys)
清除所有緩存項(xiàng) Cache.invalidateAll()
移除監(jiān)聽器
通過CacheBuilder.removalListener(RemovalListener),你可以聲明一個(gè)監(jiān)聽器,以便緩存項(xiàng)被移除時(shí)做一些額外操作。緩存項(xiàng)被移除時(shí),RemovalListener會(huì)獲取移除通知[RemovalNotification],其中包含移除原因[RemovalCause]、鍵和值。如果需要改成異步形式,可以考慮使用RemovalListeners.asynchronous(RemovalListener, Executor) 。
緩存儲(chǔ)移除的時(shí)機(jī)
Guava cache中通過CacheBuilder構(gòu)建的緩存數(shù)據(jù)不會(huì)"自動(dòng)"執(zhí)行清理和回收工作,也不會(huì)在某個(gè)緩存項(xiàng)過期后馬上清理,它會(huì)在讀/寫操作后同步進(jìn)行清理工作,只是讀操作時(shí)可能執(zhí)行的機(jī)會(huì)會(huì)少少一些。
原因:如果自動(dòng)清理緩存,就必須存在一個(gè)線程,這個(gè)線程會(huì)和用戶操作競爭共享鎖。此外,某些環(huán)境下線程創(chuàng)建可能受限制,這樣CacheBuilder就不可用了。
同時(shí)用戶可以自主的去控制清除時(shí)機(jī),比如固定的時(shí)間間隔調(diào)用Cache.cleanUp()。
2.2 刷新
刷新和回收不太一樣。正如LoadingCache.refresh(K)所聲明,刷新表示為鍵加載新值,這個(gè)過程可以是異步的。在刷新操作進(jìn)行時(shí),緩存仍然可以向其他線程返回舊值,而不像回收操作,讀緩存的線程必須等待新值加載完成。如果刷新過程拋出異常,緩存將保留舊值,而異常會(huì)在記錄到日志后被丟棄。
重載CacheLoader.reload(K, V)可以擴(kuò)展刷新時(shí)的行為,這個(gè)方法允許開發(fā)者在計(jì)算新值時(shí)使用舊的值。
示例如下:
/**
* @author mzw
* @version V1.0.0
* @description
* @data 2019-05-29 14:53
* @see
**/
public class GuavaCacheDemo {
private static final Logger LOGGER = LoggerFactory.getLogger(GuavaCacheDemo.class);
public static void main(String[] args) throws ExecutionException, InterruptedException {
//構(gòu)建移除監(jiān)聽器:非必要
RemovalListener<String, String> removalListener =
removal -> LOGGER.info("[" + removal.getKey() + ":" + removal.getValue() + "] is evicted!");
//構(gòu)建緩存
LoadingCache<String, String> cache = CacheBuilder.newBuilder()
.maximumSize(5)
.expireAfterWrite(10, TimeUnit.MINUTES)
.removalListener(removalListener)
.build(new CacheLoader<String, String>() {
@Override
public String load(String s) {
return s + " -> load";
}
@Override
public ListenableFuture<String> reload(String key, String oldValue) {
LOGGER.info(key + " > reload : " + oldValue);
ListenableFutureTask<String> reloadTask = ListenableFutureTask.create(() -> oldValue + " -> reload");
Executors.newFixedThreadPool(1).submit(reloadTask);
return reloadTask;
}
});
//放入數(shù)據(jù)
for (int i = 0; i < 3; i++) {
cache.put("key" + i, "value" + i);
}
//獲取數(shù)據(jù)
LOGGER.info("key > " + cache.getIfPresent("key"));
LOGGER.info("key1 > " + cache.getIfPresent("key1"));
cache.refresh("key1");
LOGGER.info("refresh : k > " + cache.get("key1"));
}
}
輸出結(jié)果
19:32:01.069 [main] INFO com.maozw.quartz.cache.GuavaCacheDemo - key > null
19:32:01.071 [main] INFO com.maozw.quartz.cache.GuavaCacheDemo - key1 > value1
19:32:01.077 [main] INFO com.maozw.quartz.cache.GuavaCacheDemo - k > aa
19:32:01.077 [main] INFO com.maozw.quartz.cache.GuavaCacheDemo - key1 > reload : value1
19:32:01.085 [main] INFO com.maozw.quartz.cache.GuavaCacheDemo - [key1:value1] is evicted!
19:32:01.086 [main] INFO com.maozw.quartz.cache.GuavaCacheDemo - refresh : k > value1 -> reload
統(tǒng)計(jì)
簡單介紹
CacheBuilder.recordStats():用來開啟Guava Cache的統(tǒng)計(jì)功能。統(tǒng)計(jì)打開后,Cache.stats()方法會(huì)返回CacheS tats 對(duì)象以提供如下統(tǒng)計(jì)信息:
hitRate():緩存命中率;
averageLoadPenalty():加載新值的平均時(shí)間,單位為納秒;
evictionCount():緩存項(xiàng)被回收的總數(shù),不包括顯式清除。
三 Guava Cache 解讀
在解讀之前,先提一下Guava Cache的一些組件:
Guava Cache組件中核心的類和接口列舉如下:
接口:
- Cache 頂級(jí)接口,定義get、put、invalidate等操作,以及獲取緩存統(tǒng)計(jì)數(shù)據(jù)的方法等。
- LoadingCache 繼承自Cache,并另外提供了一些當(dāng)get數(shù)據(jù)不存在時(shí)自動(dòng)去load相關(guān)key(s)所對(duì)應(yīng)的value(s)的契約(即接口中的抽象方法),具體實(shí)現(xiàn)見LoadingCache的具體實(shí)現(xiàn)類。
- RemovalListener 監(jiān)聽器接口,在緩存被移除的時(shí)候用來做一些操作,與下面的RemovalNotification、RemovalCause配套使用。很明顯這是個(gè)觀察者模式的應(yīng)用。
- Weigher 權(quán)重的接口,提供int weigh(K key, V value)抽象方法,給緩存中的Entry賦予權(quán)重信息。
抽象類:
- AbstractCache 本身是抽象類,實(shí)現(xiàn)自Cache接口,基本沒做什么實(shí)際的工作,大多數(shù)方法的實(shí)現(xiàn)只是簡單拋出UnsupportedOperationException.該抽象類提供了Cache接口的骨架,為了避免子類直接繼承Cache接口時(shí)必須實(shí)現(xiàn)所有抽象方法,這種手法在其他地方也很常見,個(gè)人覺得都算得上是一種設(shè)計(jì)模式了。
- AbstractLoadingCache 繼承自AbstractCache并實(shí)現(xiàn)了LoadingCache接口,目的也是提供一個(gè)骨架,其中的某些方法提供了在get不到數(shù)據(jù)時(shí)會(huì)自動(dòng)Load數(shù)據(jù)的契約。
- CacheLoader 抽象類,最核心的方法就是load,封裝load數(shù)據(jù)的操作,具體如何laod與該抽象類的具體子類有關(guān),只需要重寫laod方法,就可以在get不到數(shù)據(jù)時(shí)自動(dòng)去load數(shù)據(jù)到緩存中。
- ForwardingCache 裝飾器模式的用法,所有對(duì)緩存的操作都委托給其他的實(shí)現(xiàn)了Cache接口的類,該抽象類中有一個(gè)抽象方法protected abstract Cache<K, V> delegate();不難推測(cè)出來,其他的方法中均使用了該代理。即類似get(key){delegate().get(key)}
- ForwardingLoadingCache 自行推斷,不解釋。
實(shí)現(xiàn)類:
- CacheBuilder 建造者模式的應(yīng)用,緩存構(gòu)建器。構(gòu)建緩存的入口,指定緩存配置參數(shù)并初始化本地緩存。通過該類來組裝Cache,最后調(diào)用build方法來生成Cache的實(shí)例。
- CacheBuilderSpec 用來構(gòu)建CacheBuilder的實(shí)例,其中提供了一些配置參數(shù),用這些配置的參數(shù)來通過CacheBuilder實(shí)例最終構(gòu)建Cache實(shí)例。
- CacheStats 緩存使用情況統(tǒng)計(jì)信息,比如命中多少次,缺失多少次等等。
- LocalCache 本地緩存最核心的類,Cache接口實(shí)例的代理人,Cache接口提供的一些方法內(nèi)部均采委托給LocalCache實(shí)例來實(shí)現(xiàn),LocalCache的具體實(shí)現(xiàn)類似于ConcurrentHashMap,也采用了分段的方式。
CacheBuilder
CacheBuilder是緩存配置和構(gòu)建入口。
在上面例子中我們構(gòu)建緩存使用如下方式:現(xiàn)在解析CacheBuilder這個(gè)建造者的結(jié)構(gòu)
LoadingCache<String, String> cache = CacheBuilder.newBuilder()
.maximumSize(4)//緩存容量大小
.expireAfterWrite(10, TimeUnit.MINUTES)//緩存時(shí)間
.removalListener(removalListener)
.build(loader);
整個(gè)類內(nèi)容太長,分段進(jìn)行解說:
CacheBuilder 屬性
DEFAULT_INITIAL_CAPACITY://緩存的默認(rèn)初始化大小;
DEFAULT_CONCURRENCY_LEVEL:LocalCache默認(rèn)并發(fā)數(shù),用來評(píng)估Segment的個(gè)數(shù);
DEFAULT_EXPIRATION_NANOS://默認(rèn)的緩存過期時(shí)間;
initialCapacity://初始緩存大小
concurrencyLevel:用于計(jì)算有并發(fā)量
maximumSize:cache中最多能存放的緩存entry個(gè)數(shù)
expireAfterWriteNanos://緩存超時(shí)時(shí)間(起點(diǎn):緩存被創(chuàng)建或被修改)
expireAfterAccessNanos://緩存超時(shí)時(shí)間(起點(diǎn):緩存被創(chuàng)建或被修改或被訪問)
public final class CacheBuilder<K, V> {
private static final int DEFAULT_INITIAL_CAPACITY = 16;
private static final int DEFAULT_CONCURRENCY_LEVEL = 4;
private static final int DEFAULT_EXPIRATION_NANOS = 0;
private static final int DEFAULT_REFRESH_NANOS = 0;
static final Supplier<? extends StatsCounter> NULL_STATS_COUNTER = Suppliers.ofInstance(
new StatsCounter() {
...//省去無關(guān)代碼
});
static final CacheStats EMPTY_STATS = new CacheStats(0, 0, 0, 0, 0, 0);
static final Supplier<StatsCounter> CACHE_STATS_COUNTER =
new Supplier<StatsCounter>() {
@Override
public StatsCounter get() {
return new SimpleStatsCounter();
}
};
...//省去無關(guān)代碼
private static final Logger logger = Logger.getLogger(CacheBuilder.class.getName());
static final int UNSET_INT = -1;
boolean strictParsing = true;
int initialCapacity = UNSET_INT;
int concurrencyLevel = UNSET_INT;
long maximumSize = UNSET_INT;
long maximumWeight = UNSET_INT;
Weigher<? super K, ? super V> weigher;
Strength keyStrength;//鍵的引用類型(strong、weak、soft)
Strength valueStrength;//值的引用類型(strong、weak、soft)
long expireAfterWriteNanos = UNSET_INT;
long expireAfterAccessNanos = UNSET_INT;
long refreshNanos = UNSET_INT;
//key比較策略
Equivalence<Object> keyEquivalence;
Equivalence<Object> valueEquivalence;
RemovalListener<? super K, ? super V> removalListener;//元素被移除的監(jiān)聽器
Ticker ticker;
//狀態(tài)計(jì)數(shù)器,默認(rèn)為NULL_STATS_COUNTER,即不啟動(dòng)計(jì)數(shù)功能
Supplier<? extends StatsCounter> statsCounterSupplier = NULL_STATS_COUNTER;
...
}
build方法
CacheBuilder構(gòu)建緩存有兩個(gè)方法:
構(gòu)建一個(gè)具有數(shù)據(jù)加載功能的緩存,調(diào)用LocalCache構(gòu)造方法
構(gòu)建一個(gè)沒有數(shù)據(jù)加載功能的緩存,調(diào)用LocalCache構(gòu)造方法,但loader為null
public <K1 extends K, V1 extends V> LoadingCache<K1, V1> build(
CacheLoader<? super K1, V1> loader) {
checkWeightWithWeigher();
return new LocalCache.LocalLoadingCache<K1, V1>(this, loader);
}
public <K1 extends K, V1 extends V> Cache<K1, V1> build() {
checkWeightWithWeigher();
checkNonLoadingCache();
return new LocalCache.LocalManualCache<K1, V1>(this);
}
至此我們就需要先了解一下LocalCache了,因?yàn)槲覀兇藭r(shí)調(diào)用該類的構(gòu)造方法LocalCache()和實(shí)例方法LocalLoadingCache()/LocalManualCache()
LocalCache
LocalCache是guava cache的核心類。
LocalCache的數(shù)據(jù)結(jié)構(gòu)與ConcurrentHashMap很相似,都由多個(gè)segment組成,且各segment相對(duì)獨(dú)立,互不影響,所以能支持并行操作。每個(gè)segment由一個(gè)table和若干隊(duì)列組成。緩存數(shù)據(jù)存儲(chǔ)在table中,其類型為AtomicReferenceArray<ReferenceEntry<K, V>>,即一個(gè)數(shù)組,數(shù)組中每個(gè)元素是一個(gè)鏈表。兩個(gè)隊(duì)列分別是writeQueue和accessQueue,用來存儲(chǔ)寫入的數(shù)據(jù)和最近訪問的數(shù)據(jù),當(dāng)數(shù)據(jù)過期,需要刷新整體緩存(見上述示例最后一次cache.getIfPresent("key5"))時(shí),遍歷隊(duì)列,如果數(shù)據(jù)過期,則從table中刪除。
LocalCache 數(shù)據(jù)結(jié)構(gòu)
Segment<K, V>[] segments;
Segment繼承于ReetrantLock,減小鎖粒度,提高并發(fā)效率。
AtomicReferenceArray<ReferenceEntry<K, V>> table;
類似于HasmMap中的table一樣,相當(dāng)于entry的容器。
ReferenceEntry<K, V> referenceEntry;
基于引用的Entry,其實(shí)現(xiàn)類有弱引用Entry,強(qiáng)引用Entry等
ReferenceQueue<K> keyReferenceQueue;
已經(jīng)被GC,需要內(nèi)部清理的鍵引用隊(duì)列。
ReferenceQueue<V> valueReferenceQueue;
已經(jīng)被GC,需要內(nèi)部清理的值引用隊(duì)列。
Queue<ReferenceEntry<K, V>> recencyQueue;
記錄升級(jí)可訪問列表清單時(shí)的entries,當(dāng)segment上達(dá)到臨界值或發(fā)生寫操作時(shí)該隊(duì)列會(huì)被清空。
Queue<ReferenceEntry<K, V>> writeQueue;
按照寫入時(shí)間進(jìn)行排序的元素隊(duì)列,寫入一個(gè)元素時(shí)會(huì)把它加入到隊(duì)列尾部。
Queue<ReferenceEntry<K, V>> accessQueue;
按照訪問時(shí)間進(jìn)行排序的元素隊(duì)列,訪問(包括寫入)一個(gè)元素時(shí)會(huì)把它加入到隊(duì)列尾部
LocalCache 構(gòu)造器
構(gòu)造器是通過CacheBuilder的方法對(duì)變量進(jìn)行初始化。具體變量解說可參照CacheBuilder 屬性解說。
/**
* Creates a new, empty map with the specified strategy, initial capacity and concurrency level.
*/
LocalCache(
CacheBuilder<? super K, ? super V> builder, @Nullable CacheLoader<? super K, V> loader) {
concurrencyLevel = Math.min(builder.getConcurrencyLevel(), MAX_SEGMENTS);
//默認(rèn)為強(qiáng)引用
keyStrength = builder.getKeyStrength();
valueStrength = builder.getValueStrength();
keyEquivalence = builder.getKeyEquivalence();
valueEquivalence = builder.getValueEquivalence();
maxWeight = builder.getMaximumWeight();
weigher = builder.getWeigher();
expireAfterAccessNanos = builder.getExpireAfterAccessNanos();
expireAfterWriteNanos = builder.getExpireAfterWriteNanos();
refreshNanos = builder.getRefreshNanos();
removalListener = builder.getRemovalListener();
removalNotificationQueue = (removalListener == NullListener.INSTANCE)
? LocalCache.<RemovalNotification<K, V>>discardingQueue()
: new ConcurrentLinkedQueue<RemovalNotification<K, V>>();
ticker = builder.getTicker(recordsTime());
entryFactory = EntryFactory.getFactory(keyStrength, usesAccessEntries(), usesWriteEntries());
globalStatsCounter = builder.getStatsCounterSupplier().get();
defaultLoader = loader;
int initialCapacity = Math.min(builder.getInitialCapacity(), MAXIMUM_CAPACITY);
if (evictsBySize() && !customWeigher()) {
initialCapacity = Math.min(initialCapacity, (int) maxWeight);
}
...
int segmentShift = 0;
int segmentCount = 1;
while (segmentCount < concurrencyLevel
&& (!evictsBySize() || segmentCount * 20 <= maxWeight)) {
++segmentShift;
segmentCount <<= 1;
}
this.segmentShift = 32 - segmentShift;
segmentMask = segmentCount - 1;
//初始化segments大小
this.segments = newSegmentArray(segmentCount);
int segmentCapacity = initialCapacity / segmentCount;
if (segmentCapacity * segmentCount < initialCapacity) {
++segmentCapacity;
}
int segmentSize = 1;
while (segmentSize < segmentCapacity) {
segmentSize <<= 1;
}
//初始化Segments
if (evictsBySize()) {
// Ensure sum of segment max weights = overall max weights
long maxSegmentWeight = maxWeight / segmentCount + 1;
long remainder = maxWeight % segmentCount;
for (int i = 0; i < this.segments.length; ++i) {
if (i == remainder) {
maxSegmentWeight--;
}
this.segments[i] =
createSegment(segmentSize, maxSegmentWeight, builder.getStatsCounterSupplier().get());
}
} else {
for (int i = 0; i < this.segments.length; ++i) {
this.segments[i] =
createSegment(segmentSize, UNSET_INT, builder.getStatsCounterSupplier().get());
}
}
}
Segment初始化操作
初始化容器
Segment(LocalCache<K, V> map, int initialCapacity, long maxSegmentWeight,
StatsCounter statsCounter) {
this.map = map;
this.maxSegmentWeight = maxSegmentWeight;
this.statsCounter = checkNotNull(statsCounter);
initTable(newEntryArray(initialCapacity));//初始化table
keyReferenceQueue = map.usesKeyReferences()
? new ReferenceQueue<K>() : null;//key引用隊(duì)列
valueReferenceQueue = map.usesValueReferences()
? new ReferenceQueue<V>() : null;//value引用隊(duì)列
recencyQueue = map.usesAccessQueue()
? new ConcurrentLinkedQueue<ReferenceEntry<K, V>>()
: LocalCache.<ReferenceEntry<K, V>>discardingQueue();
writeQueue = map.usesWriteQueue()
? new WriteQueue<K, V>()
: LocalCache.<ReferenceEntry<K, V>>discardingQueue();//寫入元素隊(duì)列
accessQueue = map.usesAccessQueue()
? new AccessQueue<K, V>()
: LocalCache.<ReferenceEntry<K, V>>discardingQueue();//訪問元素隊(duì)列
}
以上是整個(gè)初始化流程
LocalCache put加載緩存
Cache 接口聲明
@Beta
@GwtCompatible
public interface Cache<K, V> {
void put(K key, V value);
}
LocalCache的實(shí)現(xiàn)
內(nèi)部調(diào)用segmentFor(int hash)方法,該方法返回指定的散列hash鍵值匹配的段。
然后調(diào)用Segment的put方法
@GwtCompatible(emulated = true)
class LocalCache<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V> {
@Override
public V put(K key, V value) {
checkNotNull(key);
checkNotNull(value);
int hash = hash(key);
return segmentFor(hash).put(key, hash, value, false);
}
}
segmentFor(hash).put(key, hash, value, false)
下面第四行代碼可以看出preWriteCleanup在每次put之前都會(huì)清理動(dòng)作,我在緩存儲(chǔ)移除的時(shí)機(jī)小段中進(jìn)行過提示,緩存的清除時(shí)機(jī)是在讀/寫操作的時(shí)候進(jìn)行的。
@Nullable
V put(K key, int hash, V value, boolean onlyIfAbsent) {
lock();
try {
long now = map.ticker.read();
preWriteCleanup(now);
int newCount = this.count + 1;//localCache的Count+1
if (newCount > this.threshold) { // ensure capacity 是否要進(jìn)行擴(kuò)容
expand();//擴(kuò)容
newCount = this.count + 1;
}
//獲取當(dāng)前Entry中的HashTable的Entry數(shù)組
AtomicReferenceArray<ReferenceEntry<K, V>> table = this.table;
int index = hash & (table.length() - 1);//計(jì)算散列值對(duì)應(yīng)table的索引位置
ReferenceEntry<K, V> first = table.get(index);//通過索引獲取ReferenceEntry
// Look for an existing entry.進(jìn)行遍歷 如果找到則進(jìn)行下面邏輯
for (ReferenceEntry<K, V> e = first; e != null; e = e.getNext()) {
K entryKey = e.getKey();
if (e.getHash() == hash && entryKey != null
&& map.keyEquivalence.equivalent(key, entryKey)) {
// We found an existing entry.如果找到則進(jìn)行下面邏輯
// 對(duì)應(yīng)的值引用
ValueReference<K, V> valueReference = e.getValueReference();
V entryValue = valueReference.get();//獲取值
// cache 提供基于引用的回收策略,此處可能為null:即可能會(huì)GC了
if (entryValue == null) {
++modCount;
if (valueReference.isActive()) {
enqueueNotification(key, hash, valueReference, RemovalCause.COLLECTED);
setValue(e, key, value, now);//存儲(chǔ)數(shù)據(jù),并且將新增加的元素寫入兩個(gè)隊(duì)列中
newCount = this.count; // count remains unchanged
} else {
setValue(e, key, value, now);
newCount = this.count + 1;
}
this.count = newCount; // write-volatile
evictEntries();//淘汰緩存
return null;
} else if (onlyIfAbsent) {
// Mimic
// "if (!map.containsKey(key)) ...
// else return map.get(key);
recordLockedRead(e, now);
return entryValue;
} else {
// clobber existing entry, count remains unchanged
// 如果存在且值不為null 則進(jìn)行更新value
++modCount;
enqueueNotification(key, hash, valueReference, RemovalCause.REPLACED);
setValue(e, key, value, now);
evictEntries();
return entryValue;
}
}
}
// Create a new entry. 不存在則新創(chuàng)建newEntry
++modCount;
ReferenceEntry<K, V> newEntry = newEntry(key, hash, first);
setValue(newEntry, key, value, now);
table.set(index, newEntry);
newCount = this.count + 1;
this.count = newCount; // write-volatile
evictEntries();
return null;
} finally {
unlock();
postWriteCleanup();
}
}
LocalCache preWriteCleanup(now);
簡單解說下preWriteCleanup(now);preWriteCleanup在每次put之前都會(huì)清理動(dòng)作
通過下面代碼的調(diào)用鏈:清理的是keyReferenceQueue和valueReferenceQueue這兩個(gè)隊(duì)列,這兩個(gè)隊(duì)列是引用隊(duì)列,Guava Cache為了支持弱引用和軟引用,引入了引用清空隊(duì)列.
expireEntries(now) 基于過期時(shí)間的清除方式
@GuardedBy("this")
void preWriteCleanup(long now) {
runLockedCleanup(now);
}
void runLockedCleanup(long now) {
if (tryLock()) {
try {
drainReferenceQueues();
expireEntries(now); // calls drainRecencyQueue
readCount.set(0);
} finally {
unlock();
}
}
}
//排空鍵和值引用隊(duì)列,清除包含垃圾收集的鍵或值的內(nèi)部條目
@GuardedBy("this")
void drainReferenceQueues() {
if (map.usesKeyReferences()) {
drainKeyReferenceQueue();
}
if (map.usesValueReferences()) {
drainValueReferenceQueue();
}
}
//基于過期時(shí)間的清除
@GuardedBy("this")
void expireEntries(long now) {
drainRecencyQueue();
ReferenceEntry<K, V> e;
while ((e = writeQueue.peek()) != null && map.isExpired(e, now)) {
if (!removeEntry(e, e.getHash(), RemovalCause.EXPIRED)) {
throw new AssertionError();
}
}
while ((e = accessQueue.peek()) != null && map.isExpired(e, now)) {
if (!removeEntry(e, e.getHash(), RemovalCause.EXPIRED)) {
throw new AssertionError();
}
}
}
drainKeyReferenceQueue
下面就是清理過程:
@GuardedBy("this")
void drainKeyReferenceQueue() {
Reference<? extends K> ref;
int i = 0;
while ((ref = keyReferenceQueue.poll()) != null) {
@SuppressWarnings("unchecked")
ReferenceEntry<K, V> entry = (ReferenceEntry<K, V>) ref;
map.reclaimKey(entry);
if (++i == DRAIN_MAX) {
break;
}
}
}
void reclaimKey(ReferenceEntry<K, V> entry) {
int hash = entry.getHash();
segmentFor(hash).reclaimKey(entry, hash);
}
/**
* Removes an entry whose key has been garbage collected.
*/
boolean reclaimKey(ReferenceEntry<K, V> entry, int hash) {
lock();
try {
int newCount = count - 1;
AtomicReferenceArray<ReferenceEntry<K, V>> table = this.table;
int index = hash & (table.length() - 1);
ReferenceEntry<K, V> first = table.get(index);
for (ReferenceEntry<K, V> e = first; e != null; e = e.getNext()) {
if (e == entry) {
++modCount;
ReferenceEntry<K, V> newFirst = removeValueFromChain(
first, e, e.getKey(), hash, e.getValueReference(), RemovalCause.COLLECTED);
newCount = this.count - 1;
table.set(index, newFirst);
this.count = newCount; // write-volatile
return true;
}
}
return false;
} finally {
unlock();
postWriteCleanup();
}
}
LocalCache get獲取緩存值
Cache 接口聲明
@Beta
@GwtCompatible
public interface Cache<K, V> {
V get(K key) throws ExecutionException;
}
LocalLoadingCache的實(shí)現(xiàn)
下面是get方法的調(diào)用鏈:最終執(zhí)行segmentFor(hash).get(key, hash, loader);
首先通過key 與 散列值獲取Entry,如果獲取Entry不為null;繼續(xù)獲取對(duì)應(yīng)的value;如果value不為null,并更新訪問時(shí)間,加入recencyQueue,之后進(jìn)行判斷是否進(jìn)行刷新邏輯 返回。
如果value為null,檢測(cè)是否進(jìn)行l(wèi)oading,如果是則等待,等待結(jié)果waitForLoadingValue(e, key, valueReference);;
如果獲取Entry為null,則lockedGetOrLoad(key, hash, loader); ,其方法邏輯實(shí)現(xiàn)與put方法非常相似,這里就不在做介紹。有興趣的讀者可以去看看。也需要說明的時(shí)候,此時(shí)該方法相當(dāng)有就是寫緩存,所以也會(huì)進(jìn)行加鎖。
@Override
public V get(K key) throws ExecutionException {
return localCache.getOrLoad(key);
}
V getOrLoad(K key) throws ExecutionException {
return get(key, defaultLoader);
}
V get(K key, CacheLoader<? super K, V> loader) throws ExecutionException {
int hash = hash(checkNotNull(key));
return segmentFor(hash).get(key, hash, loader);
}
V get(K key, int hash, CacheLoader<? super K, V> loader) throws ExecutionException {
checkNotNull(key);
checkNotNull(loader);
try {
if (count != 0) { // read-volatile volatile讀會(huì)刷新緩存,盡量保證可見性,如果為0那么直接load
// don't call getLiveEntry, which would ignore loading values
ReferenceEntry<K, V> e = getEntry(key, hash);
if (e != null) {//判斷通過key與hash獲取的Entry是否為null,不為null則存在
long now = map.ticker.read();//獲取當(dāng)前的訪問時(shí)間
V value = getLiveValue(e, now);//根據(jù)當(dāng)前訪問時(shí)間獲取Live的數(shù)據(jù)
if (value != null) {
recordRead(e, now);//設(shè)置entry的AccessTime。并且加入recencyQueue
statsCounter.recordHits(1);//記錄緩存命中
return scheduleRefresh(e, key, hash, value, now, loader)// 如果定時(shí)刷新,嘗試刷新value
}
//value為null,如果此時(shí)value正在刷新,那么此時(shí)等待刷新結(jié)果
ValueReference<K, V> valueReference = e.getValueReference();
if (valueReference.isLoading()) {
return waitForLoadingValue(e, key, valueReference);
}
}
}
// at this point e is either null or expired;
return lockedGetOrLoad(key, hash, loader);
} catch (ExecutionException ee) {
Throwable cause = ee.getCause();
if (cause instanceof Error) {
throw new ExecutionError((Error) cause);
} else if (cause instanceof RuntimeException) {
throw new UncheckedExecutionException(cause);
}
throw ee;
} finally {
postReadCleanup();//每次Put和get之后都要進(jìn)行一次Clean
}
}
至此針對(duì) Guava Cache的使用,以及它的運(yùn)轉(zhuǎn)流程做了一個(gè)簡單的介紹??赡芩接邢奕绻胁徽_的地方請(qǐng)指正。