一、使用
1)简单初始化:
LoadingCache<Long, Long> loadCache = CacheBuilder.newBuilder()
.expireAfterWrite(6, TimeUnit.SECONDS)
.maximumSize(100)
.build(new CacheLoader<Long, Long>() {
@Override
public Long load(Long userId) throws Exception {
return getNum(userId);
}
});
2)异步更新的初始化:
LoadingCache<Long, Long> loadCache = CacheBuilder.newBuilder()
.expireAfterWrite(6, TimeUnit.SECONDS)
.refreshAfterWrite(2, TimeUnit.SECONDS)
.maximumSize(100)
.build(CacheLoader.asyncReloading(new CacheLoader<Long, Long>() {
@Override
public Long load(Long userId) throws Exception {
return getNum(userId);
}
},Executors.newFixedThreadPool(3)));
二、源码
首先,LoadingCache是由调用触发的刷新值,而并不会定时调度主动触发。所以在请求的时候顺便验证当前值是否有效期超过refreshNanos(即refreshAfterWrite的值),是则调用refresh,这时候更新操作会标记一下状态,以便于其他后续的线程调用refresh时,不重复创建。然后具体refresh操作执行,根据是否第一次区分调用load或reload,这个见下文分析。
1)get方法:因为build里面使用是LocalLoadingCache类,所以这里看它的源码。
最终调用时Segment.get方法:
- 首先查询旧值,不存在则加锁更新返回,这时候其他get线程且没有获取旧值情况下会阻塞。
- 查询到Entry时,查询是否存在旧值,有则验证是否过期以便于刷新(刷新逻辑加下一条代码),无过期直接返回。不存在旧值时,则需要验证是否有线程已加锁load,有则等待,无则加锁load。
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();
}
}
2)scheduleRefresh方法:
- 判断该旧值是否过期,未过期直接返回,过期则尝试refresh(相当于主动调起Segment.refresh(…))。尝试标记当前线程为load状态,已存在则返回。标记成功则开始load。
- 其中load的过程,LoadingValueReference.loadFuture(key,
loader);中,如果是第一次就load,如果不是第一次,则异步reload(执行初始化时候CacheLoader的reload()),就会调用你自己定义的CacheLoader的reload方法,如果是异步的那就是异步的,如果是同步那便是同步的。
public ListenableFuture<V> loadFuture(K key, CacheLoader<? super K, V> loader) {
stopwatch.start();
V previousValue = oldValue.get();
try {
if (previousValue == null) {
V newValue = loader.load(key);
return set(newValue) ? futureValue : Futures.immediateFuture(newValue);
}
ListenableFuture<V> newValue = loader.reload(key, previousValue);
if (newValue == null) {
return Futures.immediateFuture(null);
}
// To avoid a race, make sure the refreshed value is set into loadingValueReference
// *before* returning newValue from the cache query.
return Futures.transform(newValue, new Function<V, V>() {
@Override
public V apply(V newValue) {
LoadingValueReference.this.set(newValue);
return newValue;
}
});
} catch (Throwable t) {
if (t instanceof InterruptedException) {
Thread.currentThread().interrupt();
}
return setException(t) ? futureValue : fullyFailedFuture(t);
}
}