V get(K key, CacheLoader<? super K, V> loader) throws ExecutionException {
int hash = hash(checkNotNull(key));
//这里使用了分段的hash表,原理参考ConcurrentHashMap
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 {
/**
* count表示目前java进程中活跃的缓存key的个数,
* 每做一次put会加一,失效一个则减一
* 为volatile变量,变量的改变对各个线程立即可变
* 不为0则表示本地缓存有有活跃的key
* 这样可以直接查询本地缓存获取数据
*/
if (count != 0) { // read-volatile
ReferenceEntry<K, V> e = getEntry(key, hash);
if (e != null) { //能够查询到hash对应的entry,否则也需要load
long now = map.ticker.read(); //获取当前时间
//从entry中获取数据,如果entry无效、数据不存在、数据正在load、已过期则返回null
V value = getLiveValue(e, now);
if (value != null) {
recordRead(e, now); //读取的埋点统计
statsCounter.recordHits(1); //缓存命中统计
//处理刷新逻辑,如果配置了在写入或读取多久以后刷新,满足条件的话,这里会重新load刷新数据
// (注意原来数据并未过期)
return scheduleRefresh(e, key, hash, value, now, loader);
}
//如果entry数据正在load,就等待load完成再获取数据
ValueReference<K, V> valueReference = e.getValueReference();
if (valueReference.isLoading()) {
/**
* 等待获取数据的逻辑根据引用不同略有不同
* guava分别实现了强引用、软引用、弱引用、虚引用
*/
return waitForLoadingValue(e, key, valueReference);
}
}
}
/**
* 本地缓存中的key不存在或者已过期,此时需要调用业务的load方法加载key对应的value
* 这里需要加锁,防止并发获取不一致的数据
*/
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();
}
}
缓存不命中时调用用户load方法获取数据
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; //???
AtomicReferenceArray<ReferenceEntry<K, V>> table = this.table;
int index = hash & (table.length() - 1);
ReferenceEntry<K, V> first = table.get(index);
//哈希表每个节点有一个链表
//使用分离链接法处理冲突,所有要进行链表遍历,一直找到给定的key为止
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()) {//正在load中
createNewEntry = false;
} else {
V value = valueReference.get();
if (value == null) {
enqueueNotification(
entryKey, hash, value, valueReference.getWeight(), RemovalCause.COLLECTED);
} else if (map.isExpired(e, now)) {
// This is a duplicate check, as preWriteCleanup already purged expired
// entries, but let's accomodate an incorrect expiration queue.
enqueueNotification(
entryKey, hash, value, valueReference.getWeight(), RemovalCause.EXPIRED);
} else {
recordLockedRead(e, now);
statsCounter.recordHits(1);
// we were concurrent with loading; don't consider refresh
return value;
}
// immediately reuse invalid entries
writeQueue.remove(e);
accessQueue.remove(e);
this.count = newCount; // write-volatile
}
break;
}
}
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) { //同步调用load方法获取用户提供的value
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);
}
}
缓存正在load时等待获取数据
com.google.common.cache.LocalCache.Segment#waitForLoadingValue
com.google.common.cache.LocalCache.ValueReference#waitForValue
根据不同的引用类型获取数据逻辑略有不同
默认为feature.get()