全局統計器——
Guava緩存器源碼分析——緩存統計器
Guava緩存器統計器實現:
1、CacheBuilder的靜態成員變量Supplier<StatsCounter> CACHE_STATS_COUNTER初始化時,重載的get方法,返回了一個SimpleStatsCounter實例。
2、當緩存器開啓緩存統計時(recordStats),其成員變量statsCounterSupplier被賦值爲CACHE_STATS_COUNTER,若沒開啓則爲初始值NULL_STATS_COUNTER。
3、在LocalCache的構造函數中,緩存器的全局統計器globalStatsCounter將從CacheBuilder中獲取:builder.getStatsCounterSupplier().get();
因此Guava緩存器的全局統計器實際上是SimpleStatsCounter類型。
全局統計器只有在調用getAll, getAllPresent, getIfPresent, loadAll方法時纔會更新。
段統計器——
緩存器的每個段都有自己的統計器statsCounter,在LocalCache的構造函數中,通過createSegment方法創建所有的段,同時通過 builder.getStatsCounterSupplier().get()完成對段統計器的初始化,因此段統計器也是SimpleStatsCounter類型。
當用戶想查看緩存統計信息時,會調用stats方法,將全局統計器及每一個段統計器的信息綜合起來:
public CacheStats stats() {
SimpleStatsCounter aggregator = new SimpleStatsCounter();
aggregator.incrementBy(localCache.globalStatsCounter);
for (Segment<K, V> segment : localCache.segments) {
aggregator.incrementBy(segment.statsCounter);
}
return aggregator.snapshot();
}
最終通過snapshot方法,返回包含所有統計信息的CacheStats對象:
public CacheStats snapshot() {
return new CacheStats(
hitCount.sum(),
missCount.sum(),
loadSuccessCount.sum(),
loadExceptionCount.sum(),
totalLoadTime.sum(),
evictionCount.sum());
}
SimpleStatsCounter中所有的LongAddable類型成員變量,都是通過LongAddables的create方法初始化,該靜態方法實際調用的爲 SUPPLIER.get()方法,返回一個LongAddable實例。
在LongAddables 中有一段靜態代碼段,完成了對其成員變量Supplier<LongAddable> SUPPLIER的初始化,並重載了Supplier的get方法,該get方法返回一個LongAdder對象,如果初始化出現異常,則重新初始化SUPPLIER,但是重載的get方法中,返回一個PureJavaLongAddable對象。
1、PureJavaLongAddable繼承至AtomicLong,AtomicLong使用原子方法實現了對一個Long對象的增、減、更新等操作。 比如對於++運算符 AtomicLong 可以將它持有的 Long對象原子地遞增。 PureJavaLongAddable中的方法都通過AtomicLong來實現,以保證所有操作都能原子地完成,比如add方法實際調用的即爲AtomicLong.getAndAdd,該方法將當前值加上一個數,並返回原值:
public final long getAndAdd(long delta) {
while (true) {
long current = get();
long next = current + delta;
if (compareAndSet(current, next))
return current;
}
}
2、按Guava的說法,當多條線程在更新統計數據時,而不是細粒度同步控制的情況下,LongAdder比AtomicLong更好用。當更新爭用的頻率低時,兩個類效果比較相似,當爭用頻率很高時,LongAdder的吞吐率將會大大提升,但會消耗更大的空間。
下面分析下LongAdder的add方法——
其中cells和base都爲Striped64的成員變量,cells爲數組類型,base作爲一個保底值,當不發生爭用時更新它。
public void add(long x) {
Cell[] as; long b, v; HashCode hc; Cell a; int n;
//當cells不爲空,或對base值更新失敗時,進入分支;
if ((as = cells) != null || !casBase(b = base, b + x)) {
boolean uncontended = true;
//獲取hash值;
int h = (hc = threadHashCode.get()).code;
//當cells爲空,或其長度小於1,或從cells中隨機取的cell爲空,或對隨機所取得cell更新失敗時(發生爭用),則進入分支,重新更新,若發生爭用,此時uncontended爲false,在重新更新時會使用到busy鎖;
if (as == null || (n = as.length) < 1 ||(a = as[(n - 1) & h]) == null || !(uncontended = a.cas(v = a.value, v + x)))
retryUpdate(x, hc, uncontended);
}
}
因此,LongAdder主要是通過Cell[] cells,將同步操作,轉嫁到隨機取得的cells元素上,從而使得爭用的概率大大降低,同時在發生爭用時,retryUpdate方法中還可能會對cells數據進行擴容,以降低爭用的發生:
Cell[] rs = new Cell[n << 1];
for (int i = 0; i < n; ++i)
rs[i] = as[i];
cells = rs;
雖然這確實會帶來一定的空間消耗,但緩存器本身對性能要求很高,以空間換時間是可以接受的。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.