【優雅程式碼】15-guavaCache本地快取使用及原始碼解析
阿新 • • 發佈:2022-01-15
【優雅程式碼】15-guavaCache本地快取使用及原始碼解析
歡迎關注b站賬號/公眾號【六邊形戰士夏寧】,一個要把各項指標拉滿的男人。該文章已在github目錄收錄。
螢幕前的大帥比和大漂亮如果有幫助到你的話請順手點個贊、加個收藏這對我真的很重要。別下次一定了,都不關注上哪下次一定。
- 視訊講解
- 可直接執行的完整程式碼
- 上一篇guava精選方法及eventBus觀察者模式原始碼解析
- 下一篇guava布隆過濾與限流演算法原始碼解析
1.背景
承接前一篇章的guava精選方法
2.cache
這一塊的功能設計真的很精巧,特別是佇列的設計
2.1使用
@SneakyThrows public static void cache() { // 注意兩個如果一起用有時候會有bug Cache<Integer, Integer> accessBuild = CacheBuilder.newBuilder().expireAfterAccess(1, TimeUnit.SECONDS).build(); Cache<Integer, Integer> writeBuild = CacheBuilder.newBuilder().expireAfterWrite(1, TimeUnit.SECONDS).build(); accessBuild.put(1, 1); accessBuild.put(2, 2); writeBuild.put(1, 1); writeBuild.put(2, 2); // 輸出1 System.out.println(accessBuild.getIfPresent(1)); // 輸出1 System.out.println(writeBuild.getIfPresent(1)); Thread.sleep(500); // 輸出2 System.out.println(accessBuild.getIfPresent(2)); Thread.sleep(600); // 輸出null System.out.println(accessBuild.getIfPresent(1)); // 輸出2 System.out.println(accessBuild.getIfPresent(2)); // 輸出null System.out.println(writeBuild.getIfPresent(1)); }
輸出如下
1
1
2
null
2
null
2.2核心原始碼詳解
- 構造方法
// 整體構造鏈相對簡單 // build方法 public <K1 extends K, V1 extends V> Cache<K1, V1> build() { checkWeightWithWeigher(); checkNonLoadingCache(); return new LocalCache.LocalManualCache<>(this); } // 給expireAfterAccessNanos賦值失效時間 public CacheBuilder<K, V> expireAfterAccess(long duration, TimeUnit unit) { checkState( expireAfterAccessNanos == UNSET_INT, "expireAfterAccess was already set to %s ns", expireAfterAccessNanos); checkArgument(duration >= 0, "duration cannot be negative: %s %s", duration, unit); this.expireAfterAccessNanos = unit.toNanos(duration); return this; } // 給expireAfterWriteNanos賦值失效時間 public CacheBuilder<K, V> expireAfterWrite(long duration, TimeUnit unit) { checkState( expireAfterWriteNanos == UNSET_INT, "expireAfterWrite was already set to %s ns", expireAfterWriteNanos); checkArgument(duration >= 0, "duration cannot be negative: %s %s", duration, unit); this.expireAfterWriteNanos = unit.toNanos(duration); return this; }
- put
// put方法,和老版本的ConcurrentHashMap一樣的設計模式,用segment桶 @Override public V put(K key, V value) { checkNotNull(key); checkNotNull(value); int hash = hash(key); return segmentFor(hash).put(key, hash, value, false); } V put(K key, int hash, V value, boolean onlyIfAbsent) { lock(); try { long now = map.ticker.read(); // **********重點方法:清除過期內容******** preWriteCleanup(now); int newCount = this.count + 1; if (newCount > this.threshold) { // ensure capacity expand(); newCount = this.count + 1; } AtomicReferenceArray<ReferenceEntry<K, V>> table = this.table; int index = hash & (table.length() - 1); ReferenceEntry<K, V> first = table.get(index); // Look for an existing entry. // 這裡判斷一下是不是已經有同樣的key了 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. ValueReference<K, V> valueReference = e.getValueReference(); V entryValue = valueReference.get(); if (entryValue == null) { ++modCount; if (valueReference.isActive()) { enqueueNotification( key, hash, entryValue, valueReference.getWeight(), RemovalCause.COLLECTED); setValue(e, key, value, now); newCount = this.count; // count remains unchanged } else { setValue(e, key, value, now); newCount = this.count + 1; } this.count = newCount; // write-volatile evictEntries(e); 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 ++modCount; enqueueNotification( key, hash, entryValue, valueReference.getWeight(), RemovalCause.REPLACED); setValue(e, key, value, now); evictEntries(e); return entryValue; } } } // Create a new entry. ++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(newEntry); return null; } finally { unlock(); // **********重點方法:呼叫監聽者******** postWriteCleanup(); } }
- setValue
@GuardedBy("this")
void setValue(ReferenceEntry<K, V> entry, K key, V value, long now) {
// 獲取這個包裝entry原先的值,如果原先這個key不存在,則獲取不到東西
ValueReference<K, V> previous = entry.getValueReference();
int weight = map.weigher.weigh(key, value);
checkState(weight >= 0, "Weights must be non-negative");
ValueReference<K, V> valueReference =
map.valueStrength.referenceValue(this, entry, value, weight);
// 將value寫入到entry包裝物件中
entry.setValueReference(valueReference);
// 核心方法
recordWrite(entry, weight, now);
previous.notifyNewValue(value);
}
@GuardedBy("this")
void recordWrite(ReferenceEntry<K, V> entry, int weight, long now) {
// we are already under lock, so drain the recency queue immediately
drainRecencyQueue();
totalWeight += weight;
if (map.recordsAccess()) {
// 設定訪問時間
entry.setAccessTime(now);
}
if (map.recordsWrite()) {
// 設定寫時間
entry.setWriteTime(now);
}
// **************重點方法***************這個地方將指標存了兩份佇列到末尾,因為快取時間是一致的,所以只要判斷佇列頭部就可以了
accessQueue.add(entry);
writeQueue.add(entry);
}
- WriteQueue與accessQueue
這兩個用的實現類基本一致,這裡重寫了add方法,重寫的目的是如果key一樣的entry就進行重排而不是插入
@Override
public boolean offer(ReferenceEntry<K, V> entry) {
// unlink
// 將entry的前一個和後一個進行互指
connectWriteOrder(entry.getPreviousInWriteQueue(), entry.getNextInWriteQueue());
// add to tail
// entry和對頭互指,和隊尾互指,即新增到隊尾
connectWriteOrder(head.getPreviousInWriteQueue(), entry);
connectWriteOrder(entry, head);
return true;
}
// 兩個物件進行互指
static <K, V> void connectWriteOrder(ReferenceEntry<K, V> previous, ReferenceEntry<K, V> next) {
previous.setNextInWriteQueue(next);
next.setPreviousInWriteQueue(previous);
}
- preWriteCleanup
void preWriteCleanup(long now) {
runLockedCleanup(now);
}
void runLockedCleanup(long now) {
if (tryLock()) {
try {
drainReferenceQueues();
// 核心方法繼續進入
expireEntries(now); // calls drainRecencyQueue
readCount.set(0);
} finally {
unlock();
}
}
}
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();
}
}
}
- 監聽者模式
void postWriteCleanup() {
// 核心方法繼續進入
runUnlockedCleanup();
}
void runUnlockedCleanup() {
// locked cleanup may generate notifications we can send unlocked
if (!isHeldByCurrentThread()) {
// 核心方法繼續進入
map.processPendingNotifications();
}
}
void processPendingNotifications() {
RemovalNotification<K, V> notification;
// 前面所有涉及notify的操作都會進到相應的queue中,然後在該主方法中進行回撥
while ((notification = removalNotificationQueue.poll()) != null) {
try {
// 核心流程,只要實現removalListener,通過構造方法傳進來,然後這裡就會同步呼叫實現的回撥方法
// PS第一遍看原始碼一度卡在這個位置,不知道這玩意兒就一個通知機制怎麼就移除元素了
removalListener.onRemoval(notification);
} catch (Throwable e) {
logger.log(Level.WARNING, "Exception thrown by removal listener", e);
}
}
}