为什么我们要使用本地缓存?
- 空间换时间-消耗内存空间提升速度
 
- 某些热key重复hit到很多次
 
- 缓存的总容量不会超过内存的总量
 
GuavaCache
构造
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
   | LoadingCache<String, String> build = CacheBuilder.newBuilder()
  .maximumSize(1000L)
  .concurrencyLevel(10)
  .expireAfterWrite(10, TimeUnit.SECONDS)
  .expireAfterAccess(15, TimeUnit.MINUTES)
  .recordStats()
  .removalListener(new RemovalListener<String, String>() {     public void onRemoval(RemovalNotification<String, String> removalNotification) {              } }) .build(new CacheLoader<String, String>() {          @Override     public String load(String s) throws Exception {                  return "";     } });
   | 
 
全部队列

特征
1.结构大体和concurrentHashMap一致,但是concurrentHashMap只能显示地去移除因素,而Guava Cache能自动去回收元素
2.当缓存数据超过预先设定的最大值时,利用LRU算法去移除数据,关于LRU算法的实现:https://blog.csdn.net/caoshangpa/article/details/78783749
3.设置recordStats能统计缓存的命中率,未命中率和异常率
4.不同引用级别的key value
结构

数据的加载
1.初始化时使用CacheLoader来加载数据
2.利用回调函数,实现callable接口,在get时再去指定,这样比较第一条稍微灵活一些
1 2 3 4 5 6 7
   | loadingCache.get(key, new Callable<String>() {     @Override     public String call() throws Exception {          return "";     } });
  | 
 
软引用和弱引用
1.强引用指的是Object object = new Object(); 只有有引用 垃圾回收不会回收对象
2.软引用:CacheBuilder.newBuilder().softValues() 当要发生内存溢出时,会回收该value
3.弱引用:CacheBuilder.newBuilder().weakKeys() 当gc时就会直接回收
其他使用注意点
1.使用CacheLoader来加载数据,返回null的情况会抛异常,解决办法可以为对cache.get()加trycatch处理
2.cache的超时机制是不准确的,设定存活60秒,可能为61秒或者62秒
3.如果不用load去动态加载数据,直接用cache.getIfPresent就可以了,有值则返回,无值则返回null
4.调用invalid和invalidAll来删除缓存,也可以手动用cleanUp来回收缓存
5.maximumWeight用改值可以设定缓存的最大权重,超过后采用淘汰策略LRU
6.cache.asMap将缓存转化为ConccurentHashmap
put
1.先根据当前时间用preWriteCleanup来清理无效的数据
2.不能找到entry的情况下,直接创建新的entry,储存数据,更新ccessQueue和WriteQueue
3.找到entry的情况下,如果为空,直接更新新值,更新ccessQueue和WriteQueue
4.找到entry的情况下,如果不为空,根据onlyIfAbsent来判断,true的情况直接读旧的数据,更新accessQueue,false的情况直接更新新值,更新ccessQueue和WriteQueue,然后直接返回旧数据
5.用postWriteCleanup处理被移除的数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
   | this.lock();
  try {     long now = this.map.ticker.read();     this.preWriteCleanup(now);     int newCount = this.count + 1;     if(newCount > this.threshold) {         this.expand();         newCount = this.count + 1;     }
      AtomicReferenceArray<ReferenceEntry<K, V>> table = this.table;     int index = hash & table.length() - 1;     ReferenceEntry<K, V> first = (ReferenceEntry)table.get(index);
      ReferenceEntry e;     Object entryKey;     for(e = first; e != null; e = e.getNext()) {         entryKey = e.getKey();         if(e.getHash() == hash && entryKey != null && this.map.keyEquivalence.equivalent(key, entryKey)) {             LocalCache.ValueReference<K, V> valueReference = e.getValueReference();             V entryValue = valueReference.get();             Object var15;             if(entryValue != null) {                 if(onlyIfAbsent) {                                           this.recordLockedRead(e, now);                     var15 = entryValue;                     return var15;                 }                                  ++this.modCount;                                  this.enqueueNotification(key, hash, entryValue, valueReference.getWeight(), RemovalCause.REPLACED);                                  this.setValue(e, key, value, now);                                  this.evictEntries(e);                 var15 = entryValue;                 return var15;             }                          ++this.modCount;             if(valueReference.isActive()) {                                  this.enqueueNotification(key, hash, entryValue, valueReference.getWeight(), RemovalCause.COLLECTED);                                  this.setValue(e, key, value, now);                 newCount = this.count;             } else {                                  this.setValue(e, key, value, now);                 newCount = this.count + 1;             }
              this.count = newCount;             this.evictEntries(e);             var15 = null;             return var15;         }     }          ++this.modCount;     e = this.newEntry(key, hash, first);          this.setValue(e, key, value, now);          table.set(index, e);     newCount = this.count + 1;     this.count = newCount;     this.evictEntries(e);     entryKey = null;     return entryKey; } finally {     this.unlock();          this.postWriteCleanup(); }
   | 
 
get
1.进入segment,进入table,如果直接命中调用scheduleRefresh返回结果
2.如果没有命中,进入后发现正在loading(其他线程加载数据状态),则线程阻塞,等待加载的结果
3.前两条都不符合,那就是说这个entry为null,还没有拿到数据调用lockedGetOrLoad方法获取数据
4.进入lockedGetOrLoad后,首先对segment加锁,如果没有找不到这个entry,创建新的entry,同步加载Vlaue到entry中
5.进入lockedGetOrLoad后,如果找到了这个entry,如果正在loading了,那就等待结果
6.进入lockedGetOrLoad后,如果找到了这个entry,否则如果value为null,证明被gc,或者是过期了的数据,那么这两种移入移除提醒队列,并且在writeQueue和accessQueue中remove掉,然后创建新的entry,同步加载Vlaue到entry中
7.剩下的最后一种情况为,命中数据,直接返回

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
   | if(this.count != 0) {     ReferenceEntry<K, V> e = this.getEntry(key, hash);     if(e != null) {         long now = this.map.ticker.read();         V value = this.getLiveValue(e, now);         if(value != null) {                           this.recordRead(e, now);             this.statsCounter.recordHits(1);                          Object var17 = this.scheduleRefresh(e, key, hash, value, now, loader);             return var17;         }
          LocalCache.ValueReference<K, V> valueReference = e.getValueReference();         if(valueReference.isLoading()) {                           Object var9 = this.waitForLoadingValue(e, key, valueReference);             return var9;         }     } }
  var15 = this.lockedGetOrLoad(key, hash, loader);
  | 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
   | V lockedGetOrLoad(K key, int hash, CacheLoader<? super K, V> loader) throws ExecutionException {     LocalCache.ValueReference<K, V> valueReference = null;     LocalCache.LoadingValueReference<K, V> loadingValueReference = null;     boolean createNewEntry = true;          this.lock();
      ReferenceEntry e;     try {                  long now = this.map.ticker.read();                  this.preWriteCleanup(now);         int newCount = this.count - 1;         AtomicReferenceArray<ReferenceEntry<K, V>> table = this.table;         int index = hash & table.length() - 1;         ReferenceEntry<K, V> first = (ReferenceEntry)table.get(index);
          for(e = first; e != null; e = e.getNext()) {             K entryKey = e.getKey();             if(e.getHash() == hash && entryKey != null && this.map.keyEquivalence.equivalent(key, entryKey)) {                 valueReference = e.getValueReference();                 if(valueReference.isLoading()) {                                          createNewEntry = false;                 } else {                     V value = valueReference.get();                     if(value == null) {                                                  this.enqueueNotification(entryKey, hash, value, valueReference.getWeight(), RemovalCause.COLLECTED);                     } else {                         if(!this.map.isExpired(e, now)) {                                                           this.recordLockedRead(e, now);                             this.statsCounter.recordHits(1);                             Object var16 = value;                             return var16;                         }                                                  this.enqueueNotification(entryKey, hash, value, valueReference.getWeight(), RemovalCause.EXPIRED);                     }                                                               this.writeQueue.remove(e);                     this.accessQueue.remove(e);                     this.count = newCount;                 }                 break;             }         }
          if(createNewEntry) {             loadingValueReference = new LocalCache.LoadingValueReference();             if(e == null) {                                  e = this.newEntry(key, hash, first);                 e.setValueReference(loadingValueReference);                 table.set(index, e);             } else {                 e.setValueReference(loadingValueReference);             }         }     } finally {         this.unlock();         this.postWriteCleanup();     }               if(createNewEntry) {         Object var9;         try {                          synchronized(e) {                  var9 = this.loadSync(key, hash, loadingValueReference, loader);             }         } finally {             this.statsCounter.recordMisses(1);         }
          return var9;     } else {         return this.waitForLoadingValue(e, key, valueReference);     } }
  | 
 
CaffeineCache
介绍
封装的API和Guava Cache基本上相同
在spring5.0以后使用了Caffeine代替了Guava Cache,因为Caffeine的性能比Guava Cache强很多,并且它的理想命中率也比Guava Cache高很多,这要得益于他的W-TinyLFU算法,那么W-TinyLFU又是什么呢,首先我们知道LRU是淘汰最久的数据,那么对于突发的的访问量旧的数据就被冲掉,LFU是代表访问频率最小的被淘汰掉,但是有些数据只是一段时间会访问到,所以在垃圾数据会被挤压太久,所以两者都有缺陷,这样W-TinyLFU可以说是LFU和LRU的完美结合,具体如下图所示
- 新数据进来首先进入Eden区,那么就算有突发的访问频率,也不会把这些数据给冲掉
 
- 被访问过一次以上数据会从Eden进入Probation,Probation称为缓刑区,一旦没有晋升到Protected,那么这个区的数据最有可能先死
 
- 从Probation的数据再被访问时会进入Protected区,在这个区称为保护区,这里的数据相对安全,但是一旦Protected区数据满了,被淘汰的数据,又会回到Probation区
 


图片来源
https://juejin.im/post/5b8df63c6fb9a019e04ebaf4
https://www.jianshu.com/p/38bd5f1cf2f2
参考文献
https://juejin.im/post/5b8df63c6fb9a019e04ebaf4
https://www.jianshu.com/p/38bd5f1cf2f2