概述
前面的兩篇文章(Guava Cache系列之一:如何加載緩存 和 Guava Cache系列之二:如何回收緩存)介紹了Guava Cache的使用袁梗,下面從源碼來(lái)看一下Guava Cache的設(shè)計(jì)和實(shí)現(xiàn)
源碼分析
構(gòu)造函數(shù)
LocalCache(
CacheBuilder<? super K, ? super V> builder, @Nullable CacheLoader<? super K, V> loader) {
//并發(fā)程度,根據(jù)我們傳的參數(shù)和默認(rèn)最大值中選取小者噪珊。
//如果沒有指定該參數(shù)的情況下袁波,CacheBuilder將其置為UNSET_INT即為-1
//getConcurrencyLevel方法獲取時(shí),如果為-1就返回默認(rèn)值4
//否則返回用戶傳入的參數(shù)
concurrencyLevel = Math.min(builder.getConcurrencyLevel(), MAX_SEGMENTS);
//鍵值的引用類型蚁署,沒有指定的話,默認(rèn)為強(qiáng)引用類型
keyStrength = builder.getKeyStrength();
valueStrength = builder.getValueStrength();
//判斷相同的方法搀暑,強(qiáng)引用類型就是Equivalence.equals()
keyEquivalence = builder.getKeyEquivalence();
valueEquivalence = builder.getValueEquivalence();
maxWeight = builder.getMaximumWeight();
weigher = builder.getWeigher();
expireAfterAccessNanos = builder.getExpireAfterAccessNanos();
expireAfterWriteNanos = builder.getExpireAfterWriteNanos();
refreshNanos = builder.getRefreshNanos();
//移除消息監(jiān)聽器
removalListener = builder.getRemovalListener();
//如果我們指定了移除消息監(jiān)聽器的話沥阳,會(huì)創(chuàng)建一個(gè)隊(duì)列,臨時(shí)保存移除的內(nèi)容
removalNotificationQueue = (removalListener == NullListener.INSTANCE)
? LocalCache.<RemovalNotification<K, V>>discardingQueue()
: new ConcurrentLinkedQueue<RemovalNotification<K, V>>();
ticker = builder.getTicker(recordsTime());
//創(chuàng)建新的緩存內(nèi)容(entry)的工廠自点,會(huì)根據(jù)引用類型選擇對(duì)應(yīng)的工廠
entryFactory = EntryFactory.getFactory(keyStrength, usesAccessEntries(), usesWriteEntries());
globalStatsCounter = builder.getStatsCounterSupplier().get();
defaultLoader = loader;
//初始化緩存容量桐罕,默認(rèn)為16
int initialCapacity = Math.min(builder.getInitialCapacity(), MAXIMUM_CAPACITY);
if (evictsBySize() && !customWeigher()) {
initialCapacity = Math.min(initialCapacity, (int) maxWeight);
}
// Find the lowest power-of-two segmentCount that exceeds concurrencyLevel, unless
// maximumSize/Weight is specified in which case ensure that each segment gets at least 10
// entries. The special casing for size-based eviction is only necessary because that eviction
// happens per segment instead of globally, so too many segments compared to the maximum size
// will result in random eviction behavior.
int segmentShift = 0;
int segmentCount = 1;
//根據(jù)并發(fā)程度來(lái)計(jì)算segement數(shù)組的大小(大于等于concurrencyLevel的最小的2的冪桂敛,這里即為4)
while (segmentCount < concurrencyLevel
&& (!evictsBySize() || segmentCount * 20 <= maxWeight)) {
++segmentShift;
segmentCount <<= 1;
}
//這里的segmentShift和segmentMask用來(lái)打散entry功炮,讓緩存內(nèi)容盡量均勻分布在每個(gè)segment下
this.segmentShift = 32 - segmentShift;
segmentMask = segmentCount - 1;
//這里進(jìn)行初始化segment數(shù)組,大小即為4
this.segments = newSegmentArray(segmentCount);
//每個(gè)segment的容量术唬,總?cè)萘?segment的大小薪伏,向上取整,這里就是16/4=4
int segmentCapacity = initialCapacity / segmentCount;
if (segmentCapacity * segmentCount < initialCapacity) {
++segmentCapacity;
}
//這里計(jì)算每個(gè)Segment[i]下的table的大小
int segmentSize = 1;
//SegmentSize為小于segmentCapacity的最大的2的冪粗仓,這里為4
while (segmentSize < segmentCapacity) {
segmentSize <<= 1;
}
//初始化每個(gè)segment[i]
//注:根據(jù)權(quán)重的方法使用較少嫁怀,這里走else分支
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());
}
}
}
成員變量簡(jiǎn)介如下:
static final int MAXIMUM_CAPACITY = 1 << 30:緩存最大容量,該數(shù)值必須是2的冪借浊,同時(shí)小于這個(gè)最大值2^30
static final int MAX_SEGMENTS = 1 << 16:Segment數(shù)組最大容量
static final int CONTAINS_VALUE_RETRIES = 3:containsValue方法的重試次數(shù)
static final int DRAIN_THRESHOLD = 0x3F(63):Number of cache access operations that can be buffered per segment before the cache's recency ordering information is updated. This is used to avoid lock contention by recording a memento of reads and delaying a lock acquisition until the threshold is crossed or a mutation occurs.
static final int DRAIN_MAX = 16:一次清理操作中塘淑,最大移除的entry數(shù)量
final int segmentMask:定位segment
final int segmentShift:定位segment,同時(shí)讓entry分布均勻蚂斤,盡量平均分布在每個(gè)segment[i]中
final Segment<K, V>[] segments:segment數(shù)組存捺,每個(gè)元素下都是一個(gè)HashTable
final int concurrencyLevel:并發(fā)程度,用來(lái)計(jì)算segment數(shù)組的大小曙蒸。segment數(shù)組的大小正決定了并發(fā)的程度
final Equivalence<Object> keyEquivalence:key比較方式
final Equivalence<Object> valueEquivalence:value比較方式
final Strength keyStrength:key引用類型
final Strength valueStrength:value引用類型
final long maxWeight:最大權(quán)重
final Weigher<K, V> weigher:計(jì)算每個(gè)entry權(quán)重的接口
final long expireAfterAccessNanos:一個(gè)entry訪問后多久過期
final long expireAfterWriteNanos:一個(gè)entry寫入后多久過期
final long refreshNanos:一個(gè)entry寫入多久后進(jìn)行刷新
final Queue<RemovalNotification<K, V>> removalNotificationQueue:移除監(jiān)聽器使用隊(duì)列
final RemovalListener<K, V> removalListener:entry過期移除或者gc回收(弱引用和軟引用)將會(huì)通知的監(jiān)聽器
final Ticker ticker:統(tǒng)計(jì)時(shí)間
final EntryFactory entryFactory:創(chuàng)建entry的工廠
final StatsCounter globalStatsCounter:全局緩存性能統(tǒng)計(jì)器(命中捌治、未命中、put成功逸爵、失敗次數(shù)等)
final CacheLoader<? super K, V> defaultLoader:默認(rèn)的緩存加載器
通過源碼可以看出具滴,Guava Cache的初始化和使用到的數(shù)據(jù)結(jié)構(gòu)和
ConcurrentHashMap
是很相似的。具體可以參考ConcurrentHashMap
get
核心代碼邏輯在
com.google.common.cache.LocalCache.Segment#get(K, int, com.google.common.cache.CacheLoader<? super K,V>)
V get(K key, int hash, CacheLoader<? super K, V> loader) throws ExecutionException {
checkNotNull(key);
checkNotNull(loader);
try {
if (count != 0) { // read-volatile
// don't call getLiveEntry, which would ignore loading values
ReferenceEntry<K, V> e = getEntry(key, hash);
if (e != null) {
long now = map.ticker.read();
V value = getLiveValue(e, now);
if (value != null) {
recordRead(e, now);
statsCounter.recordHits(1);
return scheduleRefresh(e, key, hash, value, now, loader);
}
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();
}
}
核心代碼在
com.google.common.cache.LocalCache.Segment#lockedGetOrLoad
V lockedGetOrLoad(K key, int hash, CacheLoader<? super K, V> loader)
throws ExecutionException {
ReferenceEntry<K, V> e;
ValueReference<K, V> valueReference = null;
LoadingValueReference<K, V> loadingValueReference = null;
boolean createNewEntry = true;
//加鎖
lock();
try {
// re-read ticker once inside the lock
long now = map.ticker.read();
preWriteCleanup(now);
int newCount = this.count - 1;
//當(dāng)前segment下的HashTable
AtomicReferenceArray<ReferenceEntry<K, V>> table = this.table;
//這里也是為什么table的大小要為2的冪(最后index范圍剛好在0-table.length()-1)
int index = hash & (table.length() - 1);
ReferenceEntry<K, V> first = table.get(index);
//在鏈表上查找
for (e = first; e != null; e = e.getNext()) {
K entryKey = e.getKey();
if (e.getHash() == hash && entryKey != null
&& map.keyEquivalence.equivalent(key, entryKey)) {
valueReference = e.getValueReference();
//如果正在載入中师倔,就不需要?jiǎng)?chuàng)建构韵,只需要等待載入完成讀取即可
if (valueReference.isLoading()) {
createNewEntry = false;
} else {
V value = valueReference.get();
// 被gc回收(在弱引用和軟引用的情況下會(huì)發(fā)生)
if (value == null) {
enqueueNotification(entryKey, hash, valueReference, RemovalCause.COLLECTED);
} else if (map.isExpired(e, now)) {
// 過期
enqueueNotification(entryKey, hash, valueReference, RemovalCause.EXPIRED);
} else {
//存在并且沒有過期,更新訪問隊(duì)列并記錄命中信息趋艘,返回value
recordLockedRead(e, now);
statsCounter.recordHits(1);
// we were concurrent with loading; don't consider refresh
return value;
}
// 對(duì)于被gc回收和過期的情況疲恢,從寫隊(duì)列和訪問隊(duì)列中移除
// 因?yàn)樵诤竺嬷匦螺d入后,會(huì)再次添加到隊(duì)列中
writeQueue.remove(e);
accessQueue.remove(e);
this.count = newCount; // write-volatile
}
break;
}
}
if (createNewEntry) {
//先創(chuàng)建一個(gè)loadingValueReference瓷胧,表示正在載入
loadingValueReference = new LoadingValueReference<K, V>();
if (e == null) {
//如果當(dāng)前鏈表為空显拳,先創(chuàng)建一個(gè)頭結(jié)點(diǎn)
e = newEntry(key, hash, first);
e.setValueReference(loadingValueReference);
table.set(index, e);
} else {
e.setValueReference(loadingValueReference);
}
}
} finally {
//解鎖
unlock();
//執(zhí)行清理
postWriteCleanup();
}
if (createNewEntry) {
try {
// Synchronizes on the entry to allow failing fast when a recursive load is
// detected. This may be circumvented when an entry is copied, but will fail fast most
// of the time.
synchronized (e) {
//異步加載
return loadSync(key, hash, loadingValueReference, loader);
}
} finally {
//記錄未命中
statsCounter.recordMisses(1);
}
} else {
// 等待加載進(jìn)來(lái)然后讀取即可
return waitForLoadingValue(e, key, valueReference);
}
}