上一篇文章讲了LocalCache是如何通过Builder构建出来的,这篇文章重点是讲localCache的原理,首先通过类图理清涉及到相关类的关系,如下图我们可以看到,guava Cache的核心就是LocalCache,LocalCache实现了ConcurrentMap,并继承了抽象的map,关于ConcurrentMap的实现可以看这篇文章,讲的是并发hashmap的实现,对理解这篇文章有帮助。对于构造LocalCache最直接的两个相关类是LocalManualCache和LocalLoadingCache。
LocalManualCache和LocalLoadingCache
顺着构造源码看下
/**
* 1、我们在代码中通过调用此处的build方法,调用LocalCache.LocalLoadingCache内部类生成LocalCache对象
*/
public <K1 extends K, V1 extends V> LoadingCache<K1, V1> build(
CacheLoader<? super K1, V1> loader) {
checkWeightWithWeigher();
return new LocalCache.LocalLoadingCache<K1, V1>(this, loader);
}
/**
* 2、调用LocalLoadingCache的超类构造LocalCache
*/
static class LocalLoadingCache<K, V>
extends LocalManualCache<K, V> implements LoadingCache<K, V> {
LocalLoadingCache(CacheBuilder<? super K, ? super V> builder,
CacheLoader<? super K, V> loader) {
super(new LocalCache<K, V>(builder, checkNotNull(loader)));
}
}
/**
* 将构造好的LocalManualCache赋值给LocalManualCache中属性
*/
static class LocalManualCache<K, V> implements Cache<K, V>, Serializable {
final LocalCache<K, V> localCache;
LocalManualCache(CacheBuilder<? super K, ? super V> builder) {
this(new LocalCache<K, V>(builder, null));
}
private LocalManualCache(LocalCache<K, V> localCache) {
this.localCache = localCache;
}
}
那么这个LoadingCache到底是什么作用呢,其实就是LocalCache对外暴露了实现的方法,所有暴露的方法都是实现了这个接口,LocalLoadingCache就是实现了这个接口,下边是部分源码:
@Override
public V get(K key) throws ExecutionException {
return localCache.getOrLoad(key);
}
@Override
public V getUnchecked(K key) {
try {
return get(key);
} catch (ExecutionException e) {
throw new UncheckedExecutionException(e.getCause());
}
}
@Override
public ImmutableMap<K, V> getAll(Iterable<? extends K> keys) throws ExecutionException {
return localCache.getAll(keys);
}
@Override
public void refresh(K key) {
localCache.refresh(key);
}
@Override
public final V apply(K key) {
return getUnchecked(key);
}
// Serialization Support
private static final long serialVersionUID = 1;
@Override
Object writeReplace() {
return new LoadingSerializationProxy<K, V>(localCache);
}
特殊的是它是LocalCache的内部静态类,这个LocalLoadingCache内部静态类只是降低了LocalCache的复杂度,它是完全独立于LocalCache的。下边是我们使用的方法都是LocalCache接口的方法
说完了LocalLoadingCache我们看下LocalManualCache的作用,LocalManualCache是LocalLoadingCache的父类,LocalManualCache实现了Cache,所以LocalManualCache具有了所有Cache的方法,LocalLoadingCache是继承自LocalManualCache同样获得了Cache的所有方法,但是LocalLoadingCache可以选择的重载LocalManualCache中的方法,这样的设计有很大的灵活性;guava cache的内部实现用的LocalCache,但是对外暴露的是LocalLoadingCache,很好隐藏了细节,总结来说
1、LocalManualCache实现了Cache,具有了所有cache方法。
2、LocalLoadingCache实现了LoadingCache,具有了所有LoadingCache方法。
3、LocalLoadingCache继承了LocalManualCache,那么对外暴露的LocalLoadingCache的方法既有自身需要的,又有cache应该具有的。
4、通过LocalLoadingCache和LocalManualCache的父子关系实现了LocalCache的细节。
Guava Cache到底是如何进行缓存的
我们现在通过类图和源码的各种继承关系理清了这两个LocalLoadingCache和LocalManualCache的重要关系,下边我们再继续深入,通过我们常用的get方法进入:
/**
* LocalLoadingCache中的get方法,localCache是父类LocalManualCache的
*/
@Override
public V get(K key) throws ExecutionException {
return localCache.getOrLoad(key);
}
/**
* 这个get和getOrLoad是AccessQueue中的方法,AccessQueue是何方神圣呢,我们通过类图梳理一下他们的关系
*/
V get(K key, CacheLoader<? super K, V> loader) throws ExecutionException {
int hash = hash(checkNotNull(key));
return segmentFor(hash).get(key, hash, loader);
}
V getOrLoad(K key) throws ExecutionException {
return get(key, defaultLoader);
}
很明显这是队列,这两个队列的作用如下
WriteQueue:按照写入时间进行排序的元素队列,写入一个元素时会把它加入到队列尾部。
AccessQueue:按照访问时间进行排序的元素队列,访问(包括写入)一个元素时会把它加入到队列尾部。
在这里就会涉及到segement的概念了,我们先把关系理清楚,首先看ConcurrentHashMap的图示,这样有助于我们理解:
在看LocalCache中的实现图示,来自
还有另外一个图,来自
LocalCache类似ConcurrentHashMap采用了分段策略,通过减小锁的粒度来提高并发,LocalCache中数据存储在Segment[]中,每个segment又包含5个队列和一个table,这个table是自定义的一种类数组的结构,每个元素都包含一个ReferenceEntry<k,v>链表,指向next entry。这些队列,前2个是key、value引用队列用以加速GC回收,后3个队列记录用户的写记录、访问记录、高频访问顺序队列用以实现LRU算法。AtomicReferenceArray是JUC包下的Doug Lea老李头设计的类:一组对象引用,其中元素支持原子性更新。这种实现比ConcurrentHashMap要复杂的多,除了多了5个引用队列之外,并且采用了ReferenceEntry的方式,引用数据存储接口,默认强引用,对应的类图为:
我们来看下ReferenceEntry接口的代码,具备了一个Entry所需要的元素
interface ReferenceEntry<K, V> {
/**
* Returns the value reference from this entry.
*/
ValueReference<K, V> getValueReference();
/**
* Sets the value reference for this entry.
*/
void setValueReference(ValueReference<K, V> valueReference);
/**
* Returns the next entry in the chain.
*/
@Nullable
ReferenceEntry<K, V> getNext();
/**
* Returns the entry's hash.
*/
int getHash();
/**
* Returns the key for this entry.
*/
@Nullable
K getKey();
/*
* Used by entries that use access order. Access entries are maintained in a doubly-linked list.
* New entries are added at the tail of the list at write time; stale entries are expired from
* the head of the list.
*/
/**
* Returns the time that this entry was last accessed, in ns.
*/
long getAccessTime();
关于强弱引用的问题可以参看这篇文章,下边我们继续看get方法,全流程看
public V get(K key) throws ExecutionException {
return localCache.getOrLoad(key);
}
V getOrLoad(K key) throws ExecutionException {
return get(key, defaultLoader);
}
/**
* 1、寻找对应的segment
* 2、寻找对应table中的元素
*/
V get(K key, CacheLoader<? super K, V> loader) throws ExecutionException {
//这里对哈希再哈希(Wang/Jenkins方法,为了进一步降低冲突)的细节暂时不讲,重点关注后面的get方法
int hash = hash(checkNotNull(key));
//根据hash找到对应的那个segment
return segmentFor(hash).get(key, hash, loader);
}
/**
* segmentFor算法
*/
Segment<K, V> segmentFor(int hash) {
return segments[(hash >>> segmentShift) & segmentMask];
}
/**
* 1、获取对应的对象
* 2、recordRead记录读取过
* 3、重置刷新
*/
V get(K key, int hash, CacheLoader<? super K, V> loader) throws ExecutionException {
//key和loader不能为null(空指针异常)
checkNotNull(key);
checkNotNull(loader);
try {
//count保存的是该sengment中缓存的数量,如果为0,就直接去载入
if (count != 0) { // read-volatile
// don't call getLiveEntry, which would ignore loading values
/**
* 看标记1
*/
ReferenceEntry<K, V> e = getEntry(key, hash);
//e != null说明缓存中已存在
if (e != null) {
long now = map.ticker.read();
/**
* getLiveValue在entry无效、过期、正在载入都会返回null,如果返回不为空,就是正常命中
* 主要看是否存活,看标记2
*/
V value = getLiveValue(e, now);
if (value != null) {
/**
* 看标记3
*/
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);
}
}
}
/**
* 如果不存在或者过期,就通过loader方法进行加载
* 看标记4
*/
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 {
//清理。通常情况下,清理操作会伴随写入进行,但是如果很久不写入的话,就需要读线程进行完成
//那么这个“很久”是多久呢?还记得前面我们设置了一个参数DRAIN_THRESHOLD=63吧
//而我们的判断条件就是if ((readCount.incrementAndGet() & DRAIN_THRESHOLD) == 0)
//条件成立,才会执行清理,也就是说,连续读取64次就会执行一次清理操作
//具体是如何清理的,后面再介绍,这里仅关注核心流程
postReadCleanup();
}
}
/**
* 标记1
*/
@Nullable
ReferenceEntry<K, V> getEntry(Object key, int hash) {
// hash链表,标记1.0
for (ReferenceEntry<K, V> e = getFirst(hash); e != null; e = e.getNext()) {
if (e.getHash() != hash) {
continue;
}
// hash值相同的,接下来找key值也相同的ReferenceEntry
K entryKey = e.getKey();
if (entryKey == null) {
/**
* 看标记1.1
*/
tryDrainReferenceQueues();//线程安全的清除搜集到的entries,使用lock机制。
continue;
}
if (map.keyEquivalence.equivalent(key, entryKey)) {
return e;
}
}
return null;
}
/**
* 标记1.1
* Cleanup collected entries when the lock is available.
*/
void tryDrainReferenceQueues() {
/**
* 看标记1.1.1
*/
if (tryLock()) {
try {
/**
* 看标记1.1.2
*/
drainReferenceQueues();
} finally {
unlock();
}
}
}
/**
*标记1.1.1
*/
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
/**
* 标记1.1.2
*/
@GuardedBy("this")
void drainReferenceQueues() {
if (map.usesKeyReferences()) {
drainKeyReferenceQueue();
}
if (map.usesValueReferences()) {
drainValueReferenceQueue();
}
}
/**
* 在segment的keyReferenceQueue的队列中排空队列,满足null的情况执行
*/
@GuardedBy("this")
void drainKeyReferenceQueue() {
Reference<? extends K> ref;
int i = 0;
while ((ref = keyReferenceQueue.poll()) != null) {
@SuppressWarnings("unchecked")
ReferenceEntry<K, V> entry = (ReferenceEntry<K, V>) ref;
map.reclaimKey(entry);
if (++i == DRAIN_MAX) {
break;
}
}
}
/**
* 找到entry对应的元素,相当于每一个元素都要判断一下是否要移除
*/
void reclaimKey(ReferenceEntry<K, V> entry) {
int hash = entry.getHash();
segmentFor(hash).reclaimKey(entry, hash);
}
/**
* Removes an entry whose key has been garbage collected.
*/
boolean reclaimKey(ReferenceEntry<K, V> entry, int hash) {
//加锁,每一个元素都要判断一下
lock();
try {
int newCount = count - 1;
AtomicReferenceArray<ReferenceEntry<K, V>> table = this.table;
int index = hash & (table.length() - 1);
ReferenceEntry<K, V> first = table.get(index);
for (ReferenceEntry<K, V> e = first; e != null; e = e.getNext()) {
if (e == entry) {
++modCount;
ReferenceEntry<K, V> newFirst = removeValueFromChain(
first, e, e.getKey(), hash, e.getValueReference(), RemovalCause.COLLECTED);
/**
* write-volatile
*/
newCount = this.count - 1;
table.set(index, newFirst);
this.count = newCount; // write-volatile
return true;
}
}
return false;
} finally {
unlock();
postWriteCleanup();
}
}
/**
* 至此元素移除
*/
@GuardedBy("this")
@Nullable
ReferenceEntry<K, V> removeValueFromChain(ReferenceEntry<K, V> first,
ReferenceEntry<K, V> entry, @Nullable K key, int hash, ValueReference<K, V> valueReference,
RemovalCause cause) {
enqueueNotification(key, hash, valueReference, cause);
writeQueue.remove(entry);
accessQueue.remove(entry);
if (valueReference.isLoading()) {
valueReference.notifyNewValue(null);
return first;
} else {
return removeEntryFromChain(first, entry);
}
}
/**
* 标记2
* key和value为空的都会删除
* 然后判断是否过期
*/
V getLiveValue(ReferenceEntry<K, V> entry, long now) {
if (entry.getKey() == null) {
tryDrainReferenceQueues();
return null;
}
V value = entry.getValueReference().get();
if (value == null) {
tryDrainReferenceQueues();
return null;
}
/**
* 看标记2.1
*/
if (map.isExpired(entry, now)) {
tryExpireEntries(now);
return null;
}
return value;
}
/**
* 标记2.1
* 是哪种过期方式
* 先判断访问过期,再是写入过期,不管是那个都会让他过期
*/
boolean isExpired(ReferenceEntry<K, V> entry, long now) {
checkNotNull(entry);
if (expiresAfterAccess()
&& (now - entry.getAccessTime() >= expireAfterAccessNanos)) {
return true;
}
if (expiresAfterWrite()
&& (now - entry.getWriteTime() >= expireAfterWriteNanos)) {
return true;
}
return false;
}
/**
* 标记3
*/
void recordRead(ReferenceEntry<K, V> entry, long now) {
if (map.recordsAccess()) {
entry.setAccessTime(now);//设置元素当前访问时间
}
recencyQueue.add(entry);
}
/**
* 标记1.0
* AtomicReferenceArray 可以确保原子的更新引用的元素。
* 为给定的hash值返回第一个entry节点.
*/
ReferenceEntry<K, V> getFirst(int hash) {
// 复制到线程安全的数组中,形成一个快照,确保读的时候,数据一致性。只会读取这个域一次。
// 此外,这样子可以提供读对于整个table的影响,因为全局的table并不会锁住。(猜测)
AtomicReferenceArray<ReferenceEntry<K, V>> table = this.table;
return table.get(hash & (table.length() - 1));
}
/**
* 标记4
*/
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;
//当前segment下的HashTable
AtomicReferenceArray<ReferenceEntry<K, V>> table = this.table;
//这里也是为什么table的大小要为2的幂(最后index范围刚好在0-table.length()-1)
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();
// 被gc回收(在弱引用和软引用的情况下会发生)
if (value == null) {
enqueueNotification(entryKey, hash, valueReference, RemovalCause.COLLECTED);
} else if (map.isExpired(e, now)) {
// 过期
enqueueNotification(entryKey, hash, valueReference, RemovalCause.EXPIRED);
} else {
//存在并且没有过期,更新访问队列并记录命中信息,返回value
recordLockedRead(e, now);
statsCounter.recordHits(1);
// we were concurrent with loading; don't consider refresh
return value;
}
// 对于被gc回收和过期的情况,从写队列和访问队列中移除
// 因为在后面重新载入后,会再次添加到队列中
writeQueue.remove(e);
accessQueue.remove(e);
this.count = newCount; // write-volatile
}
break;
}
}
if (createNewEntry) {
//先创建一个loadingValueReference,表示正在载入
loadingValueReference = new LoadingValueReference<K, V>();
if (e == null) {
//如果当前链表为空,先创建一个头结点
e = newEntry(key, hash, first);
e.setValueReference(loadingValueReference);
table.set(index, e);
} else {
e.setValueReference(loadingValueReference);
}
}
} finally {
//解锁,放在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) {
/**
* 看标记4.1
* 异步加载
*/
return loadSync(key, hash, loadingValueReference, loader);
}
} finally {
//记录未命中
statsCounter.recordMisses(1);
}
} else {
// 等待加载进来然后读取即可
return waitForLoadingValue(e, key, valueReference);
}
}
/**
* 标记4.1
*/
V loadSync(K key, int hash, LoadingValueReference<K, V> loadingValueReference,
CacheLoader<? super K, V> loader) throws ExecutionException {
/**
* 这里通过我们重写的load方法,根据key,将value载入
* 看标记4.1.1
*/
ListenableFuture<V> loadingFuture = loadingValueReference.loadFuture(key, loader);
/**
* 看标记4.1.2
*/
return getAndRecordStats(key, hash, loadingValueReference, loadingFuture);
}
/**
* 标记4.1.1
* 调用服务加值
*/
public ListenableFuture<V> loadFuture(K key, CacheLoader<? super K, V> loader) {
try {
stopwatch.start();
/**
* 一致性处理
*/
V previousValue = oldValue.get();
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) {
ListenableFuture<V> result = setException(t) ? futureValue : fullyFailedFuture(t);
if (t instanceof InterruptedException) {
Thread.currentThread().interrupt();
}
return result;
}
}
/**
* 标记4.1.2
* 等待载入,并记录载入成功或失败
*/
V getAndRecordStats(K key, int hash, LoadingValueReference<K, V> loadingValueReference,
ListenableFuture<V> newValue) throws ExecutionException {
V value = null;
try {
value = getUninterruptibly(newValue);
if (value == null) {
throw new InvalidCacheLoadException("CacheLoader returned null for key " + key + ".");
}
//性能统计信息记录载入成功
statsCounter.recordLoadSuccess(loadingValueReference.elapsedNanos());
//这个方法才是真正的将缓存内容加载完成(当前还是loadingValueReference,表示isLoading)
storeLoadedValue(key, hash, loadingValueReference, value);
return value;
} finally {
if (value == null) {
statsCounter.recordLoadException(loadingValueReference.elapsedNanos());
removeLoadingValue(key, hash, loadingValueReference);
}
}
}
boolean storeLoadedValue(K key, int hash, LoadingValueReference<K, V> oldValueReference,
V newValue) {
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);
//找到
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)) {
ValueReference<K, V> valueReference = e.getValueReference();
V entryValue = valueReference.get();
// replace the old LoadingValueReference if it's live, otherwise
// perform a putIfAbsent
if (oldValueReference == valueReference
|| (entryValue == null && valueReference != UNSET)) {
++modCount;
if (oldValueReference.isActive()) {
RemovalCause cause =
(entryValue == null) ? RemovalCause.COLLECTED : RemovalCause.REPLACED;
enqueueNotification(key, hash, oldValueReference, cause);
newCount--;
}
//LoadingValueReference变成对应引用类型的ValueReference,并进行赋值
setValue(e, key, newValue, now);
//volatile写入
this.count = newCount; // write-volatile
evictEntries();
return true;
}
// the loaded value was already clobbered
valueReference = new WeightedStrongValueReference<K, V>(newValue, 0);
enqueueNotification(key, hash, valueReference, RemovalCause.REPLACED);
return false;
}
}
++modCount;
ReferenceEntry<K, V> newEntry = newEntry(key, hash, first);
setValue(newEntry, key, newValue, now);
table.set(index, newEntry);
this.count = newCount; // write-volatile
evictEntries();
return true;
} finally {
unlock();
postWriteCleanup();
}
}
至此get方法获取,加载数据完成,那么数据是如何过期的呢?继续看
/**
* 在查询的时候刷新
*/
V scheduleRefresh(ReferenceEntry<K, V> entry, K key, int hash, V oldValue, long now,
CacheLoader<? super K, V> loader) {
if (map.refreshes() && (now - entry.getWriteTime() > map.refreshNanos)
&& !entry.getValueReference().isLoading()) {
V newValue = refresh(key, hash, loader, true);
if (newValue != null) {
return newValue;
}
}
return oldValue;
}
/**
* 异步刷新
*/
@Nullable
V refresh(K key, int hash, CacheLoader<? super K, V> loader, boolean checkTime) {
final LoadingValueReference<K, V> loadingValueReference =
insertLoadingValueReference(key, hash, checkTime);
if (loadingValueReference == null) {
return null;
}
ListenableFuture<V> result = loadAsync(key, hash, loadingValueReference, loader);
if (result.isDone()) {
try {
return Uninterruptibles.getUninterruptibly(result);
} catch (Throwable t) {
// don't let refresh exceptions propagate; error was already logged
}
}
return null;
}
/**
* 开启一个线程去做
*/
ListenableFuture<V> loadAsync(final K key, final int hash,
final LoadingValueReference<K, V> loadingValueReference, CacheLoader<? super K, V> loader) {
final ListenableFuture<V> loadingFuture = loadingValueReference.loadFuture(key, loader);
loadingFuture.addListener(
new Runnable() {
@Override
public void run() {
try {
V newValue = getAndRecordStats(key, hash, loadingValueReference, loadingFuture);
} catch (Throwable t) {
logger.log(Level.WARNING, "Exception thrown during refresh", t);
loadingValueReference.setException(t);
}
}
}, directExecutor());
return loadingFuture;
}
核心代码到这里算是读完了,但是感觉还要仔细读读好几遍才行呀,说下回收的机制:
1)基于容量回收:CacheBuilder.maximumSize(long)
2)定时回收:(看源码实在获取的时候会把所有的过期数据清空一边)
expireAfterAccess(long, TimeUnit):缓存项在给定时间内没有被读/写访问,则回收。
expireAfterWrite(long, TimeUnit):缓存项在给定时间内没有被写(创建或覆盖),则回收。
3)基于引用回收:
CacheBuilder.weakKeys():使用弱引用存储键。当键没有其它(强或软)引用时,缓存项可以被垃圾回收。因为垃圾回收仅依赖恒等式(==),使用弱引用键的缓存用==而不是equals比较键。
CacheBuilder.weakValues():使用弱引用存储值。当值没有其它(强或软)引用时,缓存项可以被垃圾回收。因为垃圾回收仅依赖恒等式(==),使用弱引用值的缓存用==而不是equals比较值。
CacheBuilder.softValues():使用软引用存储值。软引用只有在响应内存需要时,才按照全局最近最少使用的顺序回收。考虑到使用软引用的性能影响,我们通常建议使用更有性能预测性的缓存大小限定(见上文,基于容量回收)。使用软引用值的缓存同样用==而不是equals比较值。
4)显式清除:任何时候,你都可以显式地清除缓存项,而不是等到它被回收,具体如下
- 个别清除:Cache.invalidate(key)
- 批量清除:Cache.invalidateAll(keys)
- 清除所有缓存项:Cache.invalidateAll()
虽然感觉还是很弱,还是得撑着头皮总结下呀:
1、guava cache第一获取或者刷新元素的时候并不简单,各种加锁,数据一致性处理,只是减少了mysql业务等的压力
2、如果缓存中已经有数据,这个流程会简单很多
3、一个观念更加清晰:走缓存的数据第一次先查库,set数据到缓存,然后返回
4、guava cache使用segement是为了增加并发能力,否则,整个流程会慢很多
5、如果业务中缓存太多,key太多会使得太多key到一个segment,一定程度应该会慢(竞争锁)
6、segment继承了ReentrantLock
7、segment中的LocalMap,但是只作为基本变量使用,获取外部变量值,LocalCache感觉就是一个大boss,哈哈
感觉还有很多要学,欢迎大家批评
参考:
Guava Cache源码详解 https://www.cnblogs.com/dennyzhangdd/p/8981982.html
缓存框架Guava Cache部分源码分析 https://blog.51cto.com/jincheng/1827153
官方文档 https://github.com/google/guava/wiki/ImmutableCollectionsExplained
Guava LocalCache 缓存介绍及实现源码深入剖析 https://ketao1989.github.io/2014/12/19/Guava-Cache-Guide-And-Implement-Analyse/
深入Guava Cache的refresh和expire刷新机制 https://blog.csdn.net/abc86319253/article/details/53020432
缓存那些事 https://tech.meituan.com/2017/03/17/cache-about.html
Google Guava Cache 全解析 https://www.jianshu.com/p/38bd5f1cf2f2