02 初识缓存-GuavaCache

2019-05-30  本文已影响0人  花神子

在上篇文章01 初识缓存-了解缓存中简单了介绍了下缓存的历程以及几种常见的技术进行简单介绍,本着学习的目的本节针对GuavaCache进行一个专题介绍,可能理解有限欢迎指正。

一 简介

Guava Cache,是Google 出品的 Java 核心增强库的缓存部分,一种非常优秀本地缓存解决方案,提供了基于容量,时间和引用的缓存回收方式。guava cache可以按照多种策略来清理存储在其中的缓存值且保持很高的并发读写性能。基于容量的方式内部实现采用LRU算法,基于引用回收很好的利用了Java虚拟机的垃圾回收机制。其中的缓存构造器CacheBuilder采用构建者模式提供了设置好各种参数的缓存对象,缓存核心类LocalCache里面的内部类Segment与jdk1.7及以前的ConcurrentHashMap非常相似,都继承于ReetrantLock,还有六个队列,以实现丰富的本地缓存方案。

二 GuavaCache使用

2.1 缓存加载器 CacheLoader

CacheLoader是GuavaCache提供的一种加载缓存的机制,可以通过指定的键值进行计算/加载需要进行匹配的缓存数据。GuavaCache虽然提供了这种方式,但是它并不要求编程者一定需要遵循它这种方案,也可以根据自定的实际需求来定制自己加载逻辑。根据GuavaCache提出的如果想复写其缓存的加载方式,但是仍要保留“get-if-absent-compute”语义,GuavaCache在进行缓存获取的时候提供了一种解决方案:可以在调用get方法时传入一个Callable实例,来达到目的。缓存的对象可以通过Cache.put直接插入,但是自动加载是首选,因为自动加载可以更加容易的判断所有缓存信息的一致性。

Guava Cache针对开发者提供了非常友好的编程方式,使用非常简单:

下面例子分别从不同的角度:

From a CacheLoader

LoadingCache 缓存是通过一个CacheLoader来构建缓存。创建一个CacheLoader仅需要实现V load(K key) throws Exception方法即可。通过方法get(K)可以对LoadingCache进行查询。该方法要不返回已缓存的值,要不通过CacheLoader来自动加载相应的值到缓存中。

From a Callable

所有类型的Guava Cache,不论是否会自动加载,都支持get(K, Callable(V))方法。当给定键的缓存值已存在时则直接返回,否则通过指定的Callable方法进行计算并将值存放到缓存中。直到加载完成时,相应的缓存才会被更改。该方法简单实现了"if cached, return; otherwise create, cache and return"("如果有缓存则返回;否则运算、缓存、然后返回")语义。

Cache.put

使用cache.put(key, value)方法可以直接向缓存中插入值,这会直接覆盖掉给定键之前映射的值。使用Cache.asMap()视图提供的任何方法也能修改缓存。但请注意,asMap视图的任何方法都不能保证缓存项被原子地加载到缓存中。进一步说,asMap视图的原子运算在Guava Cache的原子加载范畴之外,所以相比于Cache.asMap().putIfAbsent(K,V),Cache.get(K, Callable<V>) 应该总是优先使用。

/**
 * @author mzw
 * @version V1.0.0
 * @description
 * @data 2019-05-29 14:53
 * @see
 **/
public class GuavaCache {

    private static final Logger LOGGER = LoggerFactory.getLogger(GuavaCache.class);

    public static void main(String[] args) throws ExecutionException {

        //构建缓存加载器
        CacheLoader<String, String> loader = new CacheLoader<String, String>() {
            @Override
            public String load(String key) {
                LOGGER.info(key + " is loaded from a cacheLoader!");
                return key;
            }
        };
        //构建移除监听器:非必要
        RemovalListener<String, String> removalListener =
                removal -> LOGGER.info("[" + removal.getKey() + ":" + removal.getValue() + "] is evicted!");

        //构建缓存
        LoadingCache<String, String> cache = CacheBuilder.newBuilder()
                .maximumSize(4)
                .expireAfterWrite(10, TimeUnit.MINUTES)
                .removalListener(removalListener)
                .build(loader);

        //直接放入数据
        for (int i = 0; i < 3; i++) {
            cache.put("key" + i, "value" + i);
        }
        //获取数据
        LOGGER.info("key2  > " + cache.getIfPresent("key2"));
        LOGGER.info("key  > " + cache.getIfPresent("key"));
        //From a CacheLoade
        LOGGER.info("key  > " + cache.get("key"));
        //From a Callable
        LOGGER.info("key  > " + cache.get("k1", () -> "dd"));
    }
}

输出结果

17:36:12.084 [main] INFO com.maozw.quartz.cache.GuavaCache - key2  > value2
17:36:12.086 [main] INFO com.maozw.quartz.cache.GuavaCache - key  > null
17:36:12.090 [main] INFO com.maozw.quartz.cache.GuavaCache - key is loaded from a cacheLoader!
17:36:12.092 [main] INFO com.maozw.quartz.cache.GuavaCache - key  > key
17:36:12.094 [main] INFO com.maozw.quartz.cache.GuavaCache - [key0:value0] is evicted!
17:36:12.094 [main] INFO com.maozw.quartz.cache.GuavaCache - k1  > dd

2.1 缓存回收

guava中数据的移除分为被动移除和主动移除两种,
被动移除数据的方式,简介中曾说过guava cache可以按照多种策略来清理存储在其中的缓存值且保持很高的并发读写性能。基于容量的方式内部实现采用LRU算法,基于引用回收很好的利用了Java虚拟机的垃圾回收机制。Guava Cache提供了三种基本的缓存回收方式:

基于缓存容量大小的移除

当缓存中的元素数量超过指定值时,会把不常用的键值对从cache中移除。

LoadingCache<String, String> cache = CacheBuilder.newBuilder()
                .maximumSize(4)//缓存容量大小
                .expireAfterWrite(10, TimeUnit.MINUTES)
                .removalListener(removalListener)
                .build(loader);

基于缓存时间的移除

guava提供了两个基于时间移除的方法

LoadingCache<String, String> cache = CacheBuilder.newBuilder()
                .maximumSize(4)//缓存容量大小
                .expireAfterWrite(10, TimeUnit.MINUTES)//缓存时间
                .removalListener(removalListener)
                .build(loader);

基于引用回收(Reference-based Eviction)

这种移除方式主要是基于java的垃圾回收机制,根据键或者值的引用关系决定移除

主动移除数据方式

主动移除有三种方法:

移除监听器

通过CacheBuilder.removalListener(RemovalListener),你可以声明一个监听器,以便缓存项被移除时做一些额外操作。缓存项被移除时,RemovalListener会获取移除通知[RemovalNotification],其中包含移除原因[RemovalCause]、键和值。如果需要改成异步形式,可以考虑使用RemovalListeners.asynchronous(RemovalListener, Executor) 。

缓存储移除的时机

Guava cache中通过CacheBuilder构建的缓存数据不会"自动"执行清理和回收工作,也不会在某个缓存项过期后马上清理,它会在读/写操作后同步进行清理工作,只是读操作时可能执行的机会会少少一些。
原因:如果自动清理缓存,就必须存在一个线程,这个线程会和用户操作竞争共享锁。此外,某些环境下线程创建可能受限制,这样CacheBuilder就不可用了。

同时用户可以自主的去控制清除时机,比如固定的时间间隔调用Cache.cleanUp()。

2.2 刷新

刷新和回收不太一样。正如LoadingCache.refresh(K)所声明,刷新表示为键加载新值,这个过程可以是异步的。在刷新操作进行时,缓存仍然可以向其他线程返回旧值,而不像回收操作,读缓存的线程必须等待新值加载完成。如果刷新过程抛出异常,缓存将保留旧值,而异常会在记录到日志后被丢弃。

重载CacheLoader.reload(K, V)可以扩展刷新时的行为,这个方法允许开发者在计算新值时使用旧的值。
示例如下:

/**
 * @author mzw
 * @version V1.0.0
 * @description
 * @data 2019-05-29 14:53
 * @see
 **/
public class GuavaCacheDemo {

    private static final Logger LOGGER = LoggerFactory.getLogger(GuavaCacheDemo.class);
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        //构建移除监听器:非必要
        RemovalListener<String, String> removalListener =
                removal -> LOGGER.info("[" + removal.getKey() + ":" + removal.getValue() + "] is evicted!");

        //构建缓存
        LoadingCache<String, String> cache = CacheBuilder.newBuilder()
                .maximumSize(5)
                .expireAfterWrite(10, TimeUnit.MINUTES)
                .removalListener(removalListener)
                .build(new CacheLoader<String, String>() {
                    @Override
                    public String load(String s) {
                        return s + " -> load";
                    }

                    @Override
                    public ListenableFuture<String> reload(String key, String oldValue) {
                        LOGGER.info(key + "  > reload : " + oldValue);
                        ListenableFutureTask<String> reloadTask = ListenableFutureTask.create(() -> oldValue + " -> reload");
                        Executors.newFixedThreadPool(1).submit(reloadTask);
                        return reloadTask;
                    }
                });

        //放入数据
        for (int i = 0; i < 3; i++) {
            cache.put("key" + i, "value" + i);
        }
        //获取数据
        LOGGER.info("key  > " + cache.getIfPresent("key"));
        LOGGER.info("key1  > " + cache.getIfPresent("key1"));
        cache.refresh("key1");
        LOGGER.info("refresh : k  > " + cache.get("key1"));

    }
}

输出结果

19:32:01.069 [main] INFO com.maozw.quartz.cache.GuavaCacheDemo - key  > null
19:32:01.071 [main] INFO com.maozw.quartz.cache.GuavaCacheDemo - key1  > value1
19:32:01.077 [main] INFO com.maozw.quartz.cache.GuavaCacheDemo - k  > aa
19:32:01.077 [main] INFO com.maozw.quartz.cache.GuavaCacheDemo - key1  > reload : value1
19:32:01.085 [main] INFO com.maozw.quartz.cache.GuavaCacheDemo - [key1:value1] is evicted!
19:32:01.086 [main] INFO com.maozw.quartz.cache.GuavaCacheDemo - refresh : k  > value1 -> reload

统计

简单介绍
CacheBuilder.recordStats():用来开启Guava Cache的统计功能。统计打开后,Cache.stats()方法会返回CacheS tats 对象以提供如下统计信息:
hitRate():缓存命中率;
averageLoadPenalty():加载新值的平均时间,单位为纳秒;
evictionCount():缓存项被回收的总数,不包括显式清除。

三 Guava Cache 解读

在解读之前,先提一下Guava Cache的一些组件:
Guava Cache组件中核心的类和接口列举如下:
接口:

抽象类:

实现类:

CacheBuilder

CacheBuilder是缓存配置和构建入口。

在上面例子中我们构建缓存使用如下方式:现在解析CacheBuilder这个建造者的结构

LoadingCache<String, String> cache = CacheBuilder.newBuilder()
                .maximumSize(4)//缓存容量大小
                .expireAfterWrite(10, TimeUnit.MINUTES)//缓存时间
                .removalListener(removalListener)
                .build(loader);

整个类内容太长,分段进行解说:

CacheBuilder 属性

public final class CacheBuilder<K, V> {
  private static final int DEFAULT_INITIAL_CAPACITY = 16;
  private static final int DEFAULT_CONCURRENCY_LEVEL = 4;
  private static final int DEFAULT_EXPIRATION_NANOS = 0;
  private static final int DEFAULT_REFRESH_NANOS = 0;

  static final Supplier<? extends StatsCounter> NULL_STATS_COUNTER = Suppliers.ofInstance(
      new StatsCounter() {
        ...//省去无关代码
      });
  static final CacheStats EMPTY_STATS = new CacheStats(0, 0, 0, 0, 0, 0);

  static final Supplier<StatsCounter> CACHE_STATS_COUNTER =
      new Supplier<StatsCounter>() {
    @Override
    public StatsCounter get() {
      return new SimpleStatsCounter();
    }
  };

    ...//省去无关代码

  private static final Logger logger = Logger.getLogger(CacheBuilder.class.getName());

  static final int UNSET_INT = -1;

  boolean strictParsing = true;

  int initialCapacity = UNSET_INT;
  int concurrencyLevel = UNSET_INT;
  long maximumSize = UNSET_INT;
  long maximumWeight = UNSET_INT;
  Weigher<? super K, ? super V> weigher;

  Strength keyStrength;//键的引用类型(strong、weak、soft)
  Strength valueStrength;//值的引用类型(strong、weak、soft)

  long expireAfterWriteNanos = UNSET_INT;
  long expireAfterAccessNanos = UNSET_INT;
  long refreshNanos = UNSET_INT;
  //key比较策略 
  Equivalence<Object> keyEquivalence;
  Equivalence<Object> valueEquivalence;

  RemovalListener<? super K, ? super V> removalListener;//元素被移除的监听器
  Ticker ticker;
  //状态计数器,默认为NULL_STATS_COUNTER,即不启动计数功能
  Supplier<? extends StatsCounter> statsCounterSupplier = NULL_STATS_COUNTER;

    ...
}

build方法

CacheBuilder构建缓存有两个方法:

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);
}

public <K1 extends K, V1 extends V> Cache<K1, V1> build() {
    checkWeightWithWeigher();
    checkNonLoadingCache();
    return new LocalCache.LocalManualCache<K1, V1>(this);
}

至此我们就需要先了解一下LocalCache了,因为我们此时调用该类的构造方法LocalCache()和实例方法LocalLoadingCache()/LocalManualCache()

LocalCache

LocalCache是guava cache的核心类。

LocalCache的数据结构与ConcurrentHashMap很相似,都由多个segment组成,且各segment相对独立,互不影响,所以能支持并行操作。每个segment由一个table和若干队列组成。缓存数据存储在table中,其类型为AtomicReferenceArray<ReferenceEntry<K, V>>,即一个数组,数组中每个元素是一个链表。两个队列分别是writeQueue和accessQueue,用来存储写入的数据和最近访问的数据,当数据过期,需要刷新整体缓存(见上述示例最后一次cache.getIfPresent("key5"))时,遍历队列,如果数据过期,则从table中删除。

LocalCache 数据结构

Segment<K, V>[] segments;

Segment继承于ReetrantLock,减小锁粒度,提高并发效率。

AtomicReferenceArray<ReferenceEntry<K, V>> table;

类似于HasmMap中的table一样,相当于entry的容器。

ReferenceEntry<K, V> referenceEntry;

基于引用的Entry,其实现类有弱引用Entry,强引用Entry等

ReferenceQueue<K> keyReferenceQueue;

已经被GC,需要内部清理的键引用队列。

ReferenceQueue<V> valueReferenceQueue;

已经被GC,需要内部清理的值引用队列。

Queue<ReferenceEntry<K, V>> recencyQueue;

记录升级可访问列表清单时的entries,当segment上达到临界值或发生写操作时该队列会被清空。

Queue<ReferenceEntry<K, V>> writeQueue;

按照写入时间进行排序的元素队列,写入一个元素时会把它加入到队列尾部。

Queue<ReferenceEntry<K, V>> accessQueue;

按照访问时间进行排序的元素队列,访问(包括写入)一个元素时会把它加入到队列尾部

LocalCache 构造器

构造器是通过CacheBuilder的方法对变量进行初始化。具体变量解说可参照CacheBuilder 属性解说。

  /**
   * Creates a new, empty map with the specified strategy, initial capacity and concurrency level.
   */
  LocalCache(
      CacheBuilder<? super K, ? super V> builder, @Nullable CacheLoader<? super K, V> loader) {
    concurrencyLevel = Math.min(builder.getConcurrencyLevel(), MAX_SEGMENTS);
    //默认为强引用
    keyStrength = builder.getKeyStrength();
    valueStrength = builder.getValueStrength();

    keyEquivalence = builder.getKeyEquivalence();
    valueEquivalence = builder.getValueEquivalence();

    maxWeight = builder.getMaximumWeight();
    weigher = builder.getWeigher();
    expireAfterAccessNanos = builder.getExpireAfterAccessNanos();
    expireAfterWriteNanos = builder.getExpireAfterWriteNanos();
    refreshNanos = builder.getRefreshNanos();

    removalListener = builder.getRemovalListener();
    removalNotificationQueue = (removalListener == NullListener.INSTANCE)
        ? LocalCache.<RemovalNotification<K, V>>discardingQueue()
        : new ConcurrentLinkedQueue<RemovalNotification<K, V>>();

    ticker = builder.getTicker(recordsTime());
    entryFactory = EntryFactory.getFactory(keyStrength, usesAccessEntries(), usesWriteEntries());
    globalStatsCounter = builder.getStatsCounterSupplier().get();
    defaultLoader = loader;

    int initialCapacity = Math.min(builder.getInitialCapacity(), MAXIMUM_CAPACITY);
    if (evictsBySize() && !customWeigher()) {
      initialCapacity = Math.min(initialCapacity, (int) maxWeight);
    }
     ...
    int segmentShift = 0;
    int segmentCount = 1;
    while (segmentCount < concurrencyLevel
           && (!evictsBySize() || segmentCount * 20 <= maxWeight)) {
      ++segmentShift;
      segmentCount <<= 1;
    }
    this.segmentShift = 32 - segmentShift;
    segmentMask = segmentCount - 1;
    //初始化segments大小
    this.segments = newSegmentArray(segmentCount);

    int segmentCapacity = initialCapacity / segmentCount;
    if (segmentCapacity * segmentCount < initialCapacity) {
      ++segmentCapacity;
    }

    int segmentSize = 1;
    while (segmentSize < segmentCapacity) {
      segmentSize <<= 1;
    }
    //初始化Segments
    if (evictsBySize()) {
      // Ensure sum of segment max weights = overall max weights
      long maxSegmentWeight = maxWeight / segmentCount + 1;
      long remainder = maxWeight % segmentCount;
      for (int i = 0; i < this.segments.length; ++i) {
        if (i == remainder) {
          maxSegmentWeight--;
        }
        this.segments[i] =
            createSegment(segmentSize, maxSegmentWeight, builder.getStatsCounterSupplier().get());
      }
    } else {
      for (int i = 0; i < this.segments.length; ++i) {
        this.segments[i] =
            createSegment(segmentSize, UNSET_INT, builder.getStatsCounterSupplier().get());
      }
    }
  }

Segment初始化操作

初始化容器

Segment(LocalCache<K, V> map, int initialCapacity, long maxSegmentWeight,
        StatsCounter statsCounter) {
      this.map = map;
      this.maxSegmentWeight = maxSegmentWeight;
      this.statsCounter = checkNotNull(statsCounter);
      initTable(newEntryArray(initialCapacity));//初始化table

      keyReferenceQueue = map.usesKeyReferences()
           ? new ReferenceQueue<K>() : null;//key引用队列

      valueReferenceQueue = map.usesValueReferences()
           ? new ReferenceQueue<V>() : null;//value引用队列

      recencyQueue = map.usesAccessQueue()
          ? new ConcurrentLinkedQueue<ReferenceEntry<K, V>>()
          : LocalCache.<ReferenceEntry<K, V>>discardingQueue();

      writeQueue = map.usesWriteQueue()
          ? new WriteQueue<K, V>()
          : LocalCache.<ReferenceEntry<K, V>>discardingQueue();//写入元素队列

      accessQueue = map.usesAccessQueue()
          ? new AccessQueue<K, V>()
          : LocalCache.<ReferenceEntry<K, V>>discardingQueue();//访问元素队列
}

以上是整个初始化流程


LocalCache put加载缓存

Cache 接口声明

@Beta
@GwtCompatible
public interface Cache<K, V> {
   void put(K key, V value);
}

LocalCache的实现

@GwtCompatible(emulated = true)
class LocalCache<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V> {
@Override
  public V put(K key, V value) {
    checkNotNull(key);
    checkNotNull(value);
    int hash = hash(key);
    return segmentFor(hash).put(key, hash, value, false);
  }
}

segmentFor(hash).put(key, hash, value, false)

下面第四行代码可以看出preWriteCleanup在每次put之前都会清理动作,我在缓存储移除的时机小段中进行过提示,缓存的清除时机是在读/写操作的时候进行的。

@Nullable
V put(K key, int hash, V value, boolean onlyIfAbsent) {
  lock();
  try {
    long now = map.ticker.read();
    preWriteCleanup(now);

    int newCount = this.count + 1;//localCache的Count+1
    if (newCount > this.threshold) { // ensure capacity 是否要进行扩容
      expand();//扩容
      newCount = this.count + 1;
    }
    //获取当前Entry中的HashTable的Entry数组
    AtomicReferenceArray<ReferenceEntry<K, V>> table = this.table;
    int index = hash & (table.length() - 1);//计算散列值对应table的索引位置
    ReferenceEntry<K, V> first = table.get(index);//通过索引获取ReferenceEntry

    // Look for an existing entry.进行遍历 如果找到则进行下面逻辑
    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)) {
        // We found an existing entry.如果找到则进行下面逻辑
        // 对应的值引用
        ValueReference<K, V> valueReference = e.getValueReference();
        V entryValue = valueReference.get();//获取值
        // cache 提供基于引用的回收策略,此处可能为null:即可能会GC了
        if (entryValue == null) {
          ++modCount;
          if (valueReference.isActive()) {
            enqueueNotification(key, hash, valueReference, RemovalCause.COLLECTED);
            setValue(e, key, value, now);//存储数据,并且将新增加的元素写入两个队列中
            newCount = this.count; // count remains unchanged
          } else {
            setValue(e, key, value, now);
            newCount = this.count + 1;
          }
          this.count = newCount; // write-volatile
          evictEntries();//淘汰缓存
          return null;
        } else if (onlyIfAbsent) {
          // Mimic
          // "if (!map.containsKey(key)) ...
          // else return map.get(key);
          recordLockedRead(e, now);
          return entryValue;
        } else {
          // clobber existing entry, count remains unchanged
          // 如果存在且值不为null 则进行更新value
          ++modCount;
          enqueueNotification(key, hash, valueReference, RemovalCause.REPLACED);
          setValue(e, key, value, now);
          evictEntries();
          return entryValue;
        }
      }
    }

    // Create a new entry. 不存在则新创建newEntry
    ++modCount;
    ReferenceEntry<K, V> newEntry = newEntry(key, hash, first);
    setValue(newEntry, key, value, now);
    table.set(index, newEntry);
    newCount = this.count + 1;
    this.count = newCount; // write-volatile
    evictEntries();
    return null;
  } finally {
    unlock();
    postWriteCleanup();
  }
}

LocalCache preWriteCleanup(now);

简单解说下preWriteCleanup(now);preWriteCleanup在每次put之前都会清理动作

@GuardedBy("this")
void preWriteCleanup(long now) {
  runLockedCleanup(now);
}

void runLockedCleanup(long now) {
  if (tryLock()) {
    try {
      drainReferenceQueues();
      expireEntries(now); // calls drainRecencyQueue
      readCount.set(0);
    } finally {
      unlock();
    }
  }
}
//排空键和值引用队列,清除包含垃圾收集的键或值的内部条目
@GuardedBy("this")
void drainReferenceQueues() {
  if (map.usesKeyReferences()) {
    drainKeyReferenceQueue();
  }
  if (map.usesValueReferences()) {
    drainValueReferenceQueue();
  }
}
//基于过期时间的清除
@GuardedBy("this")
void expireEntries(long now) {
  drainRecencyQueue();

  ReferenceEntry<K, V> e;
  while ((e = writeQueue.peek()) != null && map.isExpired(e, now)) {
    if (!removeEntry(e, e.getHash(), RemovalCause.EXPIRED)) {
      throw new AssertionError();
    }
  }
  while ((e = accessQueue.peek()) != null && map.isExpired(e, now)) {
    if (!removeEntry(e, e.getHash(), RemovalCause.EXPIRED)) {
      throw new AssertionError();
    }
  }
}

drainKeyReferenceQueue
下面就是清理过程:

@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;
    }
  }
}

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);
        newCount = this.count - 1;
        table.set(index, newFirst);
        this.count = newCount; // write-volatile
        return true;
      }
    }

    return false;
  } finally {
    unlock();
    postWriteCleanup();
  }
}

LocalCache get获取缓存值

Cache 接口声明

@Beta
@GwtCompatible
public interface Cache<K, V> {
   V get(K key) throws ExecutionException;
}

LocalLoadingCache的实现

下面是get方法的调用链:最终执行segmentFor(hash).get(key, hash, loader);

@Override
public V get(K key) throws ExecutionException {
  return localCache.getOrLoad(key);
}

V getOrLoad(K key) throws ExecutionException {
  return get(key, defaultLoader); 
}

V get(K key, CacheLoader<? super K, V> loader) throws ExecutionException {
  int hash = hash(checkNotNull(key));
  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 {
    if (count != 0) { // read-volatile volatile读会刷新缓存,尽量保证可见性,如果为0那么直接load
      // don't call getLiveEntry, which would ignore loading values
      ReferenceEntry<K, V> e = getEntry(key, hash);
      if (e != null) {//判断通过key与hash获取的Entry是否为null,不为null则存在
        long now = map.ticker.read();//获取当前的访问时间
        V value = getLiveValue(e, now);//根据当前访问时间获取Live的数据
        if (value != null) {
          recordRead(e, now);//设置entry的AccessTime。并且加入recencyQueue
          statsCounter.recordHits(1);//记录缓存命中
          return scheduleRefresh(e, key, hash, value, now, loader)// 如果定时刷新,尝试刷新value
        }
        //value为null,如果此时value正在刷新,那么此时等待刷新结果
        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();//每次Put和get之后都要进行一次Clean
  }
}

至此针对 Guava Cache的使用,以及它的运转流程做了一个简单的介绍。可能水平有限如果有不正确的地方请指正。


上一篇下一篇

猜你喜欢

热点阅读