V waitForLoadingValue(ReferenceEntry<K, V> e, K key, ValueReference<K, V> valueReference) throws ExecutionException { // 首先你要是一个 loading 节点 if (!valueReference.isLoading()) { thrownew AssertionError(); }
checkState(!Thread.holdsLock(e), "Recursive load of: %s", key); // don't consider expiration as we're concurrent with loading try { V value = valueReference.waitForValue(); if (value == null) { thrownew InvalidCacheLoadException("CacheLoader returned null for key " + key + "."); } // re-read ticker now that loading has completed long now = map.ticker.read(); recordRead(e, now); return value; } finally { statsCounter.recordMisses(1); } }
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;
// 要对 segment 写操作 ,先加锁 lock(); try { // re-read ticker once inside the lock long now = map.ticker.read(); preWriteCleanup(now);
// 这里基本就是 HashMap 的代码,如果没有 segment 的数组下标冲突了就拉一个链表 int newCount = this.count - 1; AtomicReferenceArray<ReferenceEntry<K, V>> table = this.table; 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();
// 如果在加载中 不做任何处理 if (valueReference.isLoading()) { createNewEntry = false; } else { V value = valueReference.get(); // 如果缓存项为 null 数据已经被删除,通知对应的 queue if (value == null) { enqueueNotification( entryKey, hash, value, valueReference.getWeight(), RemovalCause.COLLECTED); // 这个是 double check 如果缓存项过期 数据没被删除,通知对应的 queue } elseif (map.isExpired(e, now)) { // This is a duplicate check, as preWriteCleanup already purged expired // entries, but let's accommodate an incorrect expiration queue. enqueueNotification( entryKey, hash, value, valueReference.getWeight(), RemovalCause.EXPIRED); // 再次看到的时候这个位置有值了直接返回 } else { recordLockedRead(e, now); statsCounter.recordHits(1); return value; }
// 没有 loading ,创建一个 loading 节点 if (createNewEntry) { loadingValueReference = new LoadingValueReference<>();
if (e == null) { e = newEntry(key, hash, first); e.setValueReference(loadingValueReference); table.set(index, e); } else { e.setValueReference(loadingValueReference); } } } finally { unlock(); 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 { // The entry already exists. Wait for loading. return waitForLoadingValue(e, key, valueReference); } }
总结
结合上面图以及源码我们发现在整个流程中 GuavaCache 是没有额外的线程去做数据清理和刷新的,基本都是通过 Get 方法来触发这些动作 ,减少了设计的复杂性和降低了系统开销。
简单回顾下 Get 的流程以及在每个阶段做的事情,返回的值。首先判断缓存是否过期然后判断是否需要刷新,如果过期了就调用 loading 去同步加载数据(其他线程阻塞),如果是仅仅需要刷新调用 reloading 异步加载(其他线程返回老值)。