Guava Cache源码:从特性说起剖析Guava Cache源码实现

目录

1.CacheBuilder

1.1 缓存属性

1.2 缓存构造

1.2.1 LocalManualCache类

1.2.2 LocalLoadingCache类

2. LocalCache类

2.1 LocalCache的构造

2.1.1 EntryFactory工厂类

2.1.2 Segment[] segments域字段,>

2.1.3 Segment属性与构造函数

2.2 LocalCache核心接口实现

2.2.1 put流程

2.2.2 get流程

2.2.3 getOrLoad流程

2.2.4 refresh流程

3. Segment核心流程

3.1 put流程

3.2 get流程

3.3 refresh流程

4. RemovalListener流程

4.1 RemovalListener接口

4.2 RemovalNotification对象

4.3 RemovalListeners

5.  总结


1.CacheBuilder

1.1 缓存属性

Guava Cache通过常用的builder模式来构造缓存,builder类为CacheBuilder,可以设置的属性包括:

  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;  

  int initialCapacity = UNSET_INT;     //设置初始化大小,默认16
  int concurrencyLevel = UNSET_INT;    //设置并发级别,默认4
  long maximumSize = UNSET_INT;        //缓存最大大小
  long maximumWeight = UNSET_INT;      //缓存最大权重
  Weigher weigher;   //缓存权重计算器

  Strength keyStrength; 				   //key引用强度,强引用 or 弱引用
  Strength valueStrength;		    	   //value引用强度

  long expireAfterWriteNanos = UNSET_INT;		//写过期时间
  long expireAfterAccessNanos = UNSET_INT;		//读过期时间
  long refreshNanos = UNSET_INT;				//刷新周期

  Equivalence keyEquivalence;			//key比较器
  Equivalence valueEquivalence;			//value比较器

  RemovalListener removalListener;		//entry移除监听器
  Ticker ticker;												//计时器

  Supplier statsCounterSupplier = NULL_STATS_COUNTER; //缓存统计器 
  

1.2 缓存构造

最后通过CacheBuilder的build方法构造缓存,这里分为两种情况:

  1. 不包含加载器CacheLoader,构造缓存类:LocalManualCache

  2. 包含加载器CacheLoader,构造缓存类:LocalLoadingCache

代码如下:

  /**
   * Builds a cache, which either returns an already-loaded value for a given key or atomically
   * computes or retrieves it using the supplied {@code CacheLoader}. If another thread is currently
   * loading the value for this key, simply waits for that thread to finish and returns its loaded
   * value. Note that multiple threads can concurrently load values for distinct keys.
   *
   * 

This method does not alter the state of this {@code CacheBuilder} instance, so it can be * invoked again to create multiple independent caches. * * @param loader the cache loader used to obtain new values * @return a cache having the requested features */ public LoadingCache build( CacheLoader loader) { checkWeightWithWeigher(); return new LocalCache.LocalLoadingCache(this, loader); } /** * Builds a cache which does not automatically load values when keys are requested. * *

Consider {@link #build(CacheLoader)} instead, if it is feasible to implement a * {@code CacheLoader}. * *

This method does not alter the state of this {@code CacheBuilder} instance, so it can be * invoked again to create multiple independent caches. * * @return a cache having the requested features * @since 11.0 */ public Cache build() { checkWeightWithWeigher(); checkNonLoadingCache(); return new LocalCache.LocalManualCache(this); }

下面看一下这两个缓存类的定义:

Guava Cache源码:从特性说起剖析Guava Cache源码实现_第1张图片

1.2.1 LocalManualCache类

 static class LocalManualCache implements Cache, Serializable {
    final LocalCache localCache;

    LocalManualCache(CacheBuilder builder) {
      this(new LocalCache(builder, null));
    }

    private LocalManualCache(LocalCache localCache) {
      this.localCache = localCache;
    }

    // Cache methods

    @Override
    @Nullable
    public V getIfPresent(Object key) {
      return localCache.getIfPresent(key);
    }

    @Override
    public V get(K key, final Callable valueLoader) throws ExecutionException {
      checkNotNull(valueLoader);
      return localCache.get(
          key,
          new CacheLoader() {
            @Override
            public V load(Object key) throws Exception {
              return valueLoader.call();
            }
          });
    }

    @Override
    public ImmutableMap getAllPresent(Iterable keys) {
      return localCache.getAllPresent(keys);
    }

    @Override
    public void put(K key, V value) {
      localCache.put(key, value);
    }

    @Override
    public void putAll(Map m) {
      localCache.putAll(m);
    }

    @Override
    public void invalidate(Object key) {
      checkNotNull(key);
      localCache.remove(key);
    }

    @Override
    public void invalidateAll(Iterable keys) {
      localCache.invalidateAll(keys);
    }

    @Override
    public void invalidateAll() {
      localCache.clear();
    }

    @Override
    public long size() {
      return localCache.longSize();
    }

    @Override
    public ConcurrentMap asMap() {
      return localCache;
    }

    @Override
    public CacheStats stats() {
      SimpleStatsCounter aggregator = new SimpleStatsCounter();
      aggregator.incrementBy(localCache.globalStatsCounter);
      for (Segment segment : localCache.segments) {
        aggregator.incrementBy(segment.statsCounter);
      }
      return aggregator.snapshot();
    }

    @Override
    public void cleanUp() {
      localCache.cleanUp();
    }

    // Serialization Support

    private static final long serialVersionUID = 1;

    Object writeReplace() {
      return new ManualSerializationProxy(localCache);
    }
  }

可以看到LocalManualCache实现Cache接口,运用组合模式,在构造函数中构造了LocalCache类,Cache接口方法的实现都是委托给LocalCache完成的;

1.2.2 LocalLoadingCache类

  static class LocalLoadingCache extends LocalManualCache
      implements LoadingCache {

    LocalLoadingCache(
        CacheBuilder builder, CacheLoader loader) {
      super(new LocalCache(builder, checkNotNull(loader)));
    }

    // LoadingCache methods

    @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 getAll(Iterable 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(localCache);
    }
  }
}

 LocalLoadingCache继承LocalManualCache类,并实现LoadingCache接口,在构造函数中同样构造了LocalCache类,LoadingCache接口方法同样是委托给LocalCache来完成的,下面着重分析下LocalCache的构造以及原理;

2. LocalCache类

Guava Cache源码:从特性说起剖析Guava Cache源码实现_第2张图片

LocalCache继承结构如上,主要实现了AbstractMap和ConcurrentMap接口;

这里着重分析一下LocalCache的构造函数,以及几个核心接口源码实现;

2.1 LocalCache的构造

  /**
   * Creates a new, empty map with the specified strategy, initial capacity and concurrency level.
   */
  LocalCache(
      CacheBuilder builder, @Nullable CacheLoader 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.>discardingQueue()
            : new ConcurrentLinkedQueue>();

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

    // Find the lowest power-of-two segmentCount that exceeds concurrencyLevel, unless
    // maximumSize/Weight is specified in which case ensure that each segment gets at least 10
    // entries. The special casing for size-based eviction is only necessary because that eviction
    // happens per segment instead of globally, so too many segments compared to the maximum size
    // will result in random eviction behavior.
    int segmentShift = 0;
    int segmentCount = 1;
    while (segmentCount < concurrencyLevel && (!evictsBySize() || segmentCount * 20 <= maxWeight)) {
      ++segmentShift;
      segmentCount <<= 1;
    }
    this.segmentShift = 32 - segmentShift;
    segmentMask = segmentCount - 1;

    this.segments = newSegmentArray(segmentCount);

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

    int segmentSize = 1;
    while (segmentSize < segmentCapacity) {
      segmentSize <<= 1;
    }

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

这里构造函数中包含的属性也主要是上面CacheBuilder传递的,下面着重解析如下几个属性:

  • EntryFactory

  • segments域字段

2.1.1 EntryFactory工厂类

  /**
   * Creates new entries.
   */
  enum EntryFactory {
    STRONG {
      @Override
       ReferenceEntry newEntry(
          Segment segment, K key, int hash, @Nullable ReferenceEntry next) {
        return new StrongEntry(key, hash, next);
      }
    },
    STRONG_ACCESS {
      @Override
       ReferenceEntry newEntry(
          Segment segment, K key, int hash, @Nullable ReferenceEntry next) {
        return new StrongAccessEntry(key, hash, next);
      }

      @Override
       ReferenceEntry copyEntry(
          Segment segment, ReferenceEntry original, ReferenceEntry newNext) {
        ReferenceEntry newEntry = super.copyEntry(segment, original, newNext);
        copyAccessEntry(original, newEntry);
        return newEntry;
      }
    },
    STRONG_WRITE {
      @Override
       ReferenceEntry newEntry(
          Segment segment, K key, int hash, @Nullable ReferenceEntry next) {
        return new StrongWriteEntry(key, hash, next);
      }

      @Override
       ReferenceEntry copyEntry(
          Segment segment, ReferenceEntry original, ReferenceEntry newNext) {
        ReferenceEntry newEntry = super.copyEntry(segment, original, newNext);
        copyWriteEntry(original, newEntry);
        return newEntry;
      }
    },
    STRONG_ACCESS_WRITE {
      @Override
       ReferenceEntry newEntry(
          Segment segment, K key, int hash, @Nullable ReferenceEntry next) {
        return new StrongAccessWriteEntry(key, hash, next);
      }

      @Override
       ReferenceEntry copyEntry(
          Segment segment, ReferenceEntry original, ReferenceEntry newNext) {
        ReferenceEntry newEntry = super.copyEntry(segment, original, newNext);
        copyAccessEntry(original, newEntry);
        copyWriteEntry(original, newEntry);
        return newEntry;
      }
    },
    WEAK {
      @Override
       ReferenceEntry newEntry(
          Segment segment, K key, int hash, @Nullable ReferenceEntry next) {
        return new WeakEntry(segment.keyReferenceQueue, key, hash, next);
      }
    },
    WEAK_ACCESS {
      @Override
       ReferenceEntry newEntry(
          Segment segment, K key, int hash, @Nullable ReferenceEntry next) {
        return new WeakAccessEntry(segment.keyReferenceQueue, key, hash, next);
      }

      @Override
       ReferenceEntry copyEntry(
          Segment segment, ReferenceEntry original, ReferenceEntry newNext) {
        ReferenceEntry newEntry = super.copyEntry(segment, original, newNext);
        copyAccessEntry(original, newEntry);
        return newEntry;
      }
    },
    WEAK_WRITE {
      @Override
       ReferenceEntry newEntry(
          Segment segment, K key, int hash, @Nullable ReferenceEntry next) {
        return new WeakWriteEntry(segment.keyReferenceQueue, key, hash, next);
      }

      @Override
       ReferenceEntry copyEntry(
          Segment segment, ReferenceEntry original, ReferenceEntry newNext) {
        ReferenceEntry newEntry = super.copyEntry(segment, original, newNext);
        copyWriteEntry(original, newEntry);
        return newEntry;
      }
    },
    WEAK_ACCESS_WRITE {
      @Override
       ReferenceEntry newEntry(
          Segment segment, K key, int hash, @Nullable ReferenceEntry next) {
        return new WeakAccessWriteEntry(segment.keyReferenceQueue, key, hash, next);
      }

      @Override
       ReferenceEntry copyEntry(
          Segment segment, ReferenceEntry original, ReferenceEntry newNext) {
        ReferenceEntry newEntry = super.copyEntry(segment, original, newNext);
        copyAccessEntry(original, newEntry);
        copyWriteEntry(original, newEntry);
        return newEntry;
      }
    };

    /**
     * Masks used to compute indices in the following table.
     */
    static final int ACCESS_MASK = 1;
    static final int WRITE_MASK = 2;
    static final int WEAK_MASK = 4;

    /**
     * Look-up table for factories.
     */
    static final EntryFactory[] factories = {
      STRONG,
      STRONG_ACCESS,
      STRONG_WRITE,
      STRONG_ACCESS_WRITE,
      WEAK,
      WEAK_ACCESS,
      WEAK_WRITE,
      WEAK_ACCESS_WRITE,
    };

    static EntryFactory getFactory(
        Strength keyStrength, boolean usesAccessQueue, boolean usesWriteQueue) {
      int flags =
          ((keyStrength == Strength.WEAK) ? WEAK_MASK : 0)
              | (usesAccessQueue ? ACCESS_MASK : 0)
              | (usesWriteQueue ? WRITE_MASK : 0);
      return factories[flags];
    }

    /**
     * Creates a new entry.
     *
     * @param segment to create the entry for
     * @param key of the entry
     * @param hash of the key
     * @param next entry in the same bucket
     */
    abstract  ReferenceEntry newEntry(
        Segment segment, K key, int hash, @Nullable ReferenceEntry next);

    /**
     * Copies an entry, assigning it a new {@code next} entry.
     *
     * @param original the entry to copy
     * @param newNext entry in the same bucket
     */
    // Guarded By Segment.this
     ReferenceEntry copyEntry(
        Segment segment, ReferenceEntry original, ReferenceEntry newNext) {
      return newEntry(segment, original.getKey(), original.getHash(), newNext);
    }

    // Guarded By Segment.this
     void copyAccessEntry(ReferenceEntry original, ReferenceEntry newEntry) {
      // TODO(fry): when we link values instead of entries this method can go
      // away, as can connectAccessOrder, nullifyAccessOrder.
      newEntry.setAccessTime(original.getAccessTime());

      connectAccessOrder(original.getPreviousInAccessQueue(), newEntry);
      connectAccessOrder(newEntry, original.getNextInAccessQueue());

      nullifyAccessOrder(original);
    }

    // Guarded By Segment.this
     void copyWriteEntry(ReferenceEntry original, ReferenceEntry newEntry) {
      // TODO(fry): when we link values instead of entries this method can go
      // away, as can connectWriteOrder, nullifyWriteOrder.
      newEntry.setWriteTime(original.getWriteTime());

      connectWriteOrder(original.getPreviousInWriteQueue(), newEntry);
      connectWriteOrder(newEntry, original.getNextInWriteQueue());

      nullifyWriteOrder(original);
    }
  }

EntryFactory主要用于构造ReferenceEntry对象,包含new方法和copy方法;

ReferenceEntry表示每一个(key,value)缓存对象元素的实例对象;

这里主要是根据key的引用强度(强引用 or 弱引用)以及过期时间是根据writetime or accesstime分别实例化不同的对象,主要类继承结构如下:

Guava Cache源码:从特性说起剖析Guava Cache源码实现_第3张图片

2.1.2 Segment[] segments域字段

segments表示缓存对象的存储结构,上面构造函数中详细说明了Segment的构造以及segmentSize和maxSegmentWeight是如何进行分配的;

2.1.3 Segment属性与构造函数

下面是Segment的属性和构造函数,通过注释可以较好的理解每个字段的含义;

static class Segment extends ReentrantLock {

  @Weak final LocalCache map;

  /**
     * The number of live elements in this segment's region.
     */
  volatile int count;

  /**
     * The weight of the live elements in this segment's region.
     */
  @GuardedBy("this")
  long totalWeight;

  /**
     * Number of updates that alter the size of the table. This is used during bulk-read methods to
     * make sure they see a consistent snapshot: If modCounts change during a traversal of segments
     * loading size or checking containsValue, then we might have an inconsistent view of state so
     * (usually) must retry.
     */
  int modCount;

  /**
     * The table is expanded when its size exceeds this threshold. (The value of this field is
     * always {@code (int) (capacity * 0.75)}.)
     */
  int threshold;

  /**
     * The per-segment table.
     */
  volatile AtomicReferenceArray> table;

  /**
     * The maximum weight of this segment. UNSET_INT if there is no maximum.
     */
  final long maxSegmentWeight;

  /**
     * The key reference queue contains entries whose keys have been garbage collected, and which
     * need to be cleaned up internally.
     */
  final ReferenceQueue keyReferenceQueue;

  /**
     * The value reference queue contains value references whose values have been garbage collected,
     * and which need to be cleaned up internally.
     */
  final ReferenceQueue valueReferenceQueue;

  /**
     * The recency queue is used to record which entries were accessed for updating the access
     * list's ordering. It is drained as a batch operation when either the DRAIN_THRESHOLD is
     * crossed or a write occurs on the segment.
     */
  final Queue> recencyQueue;

  /**
     * A counter of the number of reads since the last write, used to drain queues on a small
     * fraction of read operations.
     */
  final AtomicInteger readCount = new AtomicInteger();

  /**
     * A queue of elements currently in the map, ordered by write time. Elements are added to the
     * tail of the queue on write.
     */
  @GuardedBy("this")
  final Queue> writeQueue;

  /**
     * A queue of elements currently in the map, ordered by access time. Elements are added to the
     * tail of the queue on access (note that writes count as accesses).
     */
  @GuardedBy("this")
  final Queue> accessQueue;

  /** Accumulates cache statistics. */
  final StatsCounter statsCounter;

  Segment(
    LocalCache map,
    int initialCapacity,
    long maxSegmentWeight,
    StatsCounter statsCounter) {
    this.map = map;
    this.maxSegmentWeight = maxSegmentWeight;
    this.statsCounter = checkNotNull(statsCounter);
    initTable(newEntryArray(initialCapacity));

    keyReferenceQueue = map.usesKeyReferences() ? new ReferenceQueue() : null;

    valueReferenceQueue = map.usesValueReferences() ? new ReferenceQueue() : null;

    recencyQueue =
      map.usesAccessQueue()
      ? new ConcurrentLinkedQueue>()
      : LocalCache.>discardingQueue();

    writeQueue =
      map.usesWriteQueue()
      ? new WriteQueue()
      : LocalCache.>discardingQueue();

    accessQueue =
      map.usesAccessQueue()
      ? new AccessQueue()
      : LocalCache.>discardingQueue();
  }

  AtomicReferenceArray> newEntryArray(int size) {
    return new AtomicReferenceArray>(size);
  }
}

2.2 LocalCache核心接口实现

理解了上面LocalCache的构造以及Segment[] segments的构造,下面着重分析下几个核心接口的实现原理;

2.2.1 put流程

  @Override
  public V put(K key, V value) {
    checkNotNull(key);
    checkNotNull(value);
    int hash = hash(key);
    return segmentFor(hash).put(key, hash, value, false);
  }

  @Override
  public V putIfAbsent(K key, V value) {
    checkNotNull(key);
    checkNotNull(value);
    int hash = hash(key);
    return segmentFor(hash).put(key, hash, value, true);
  }

put和putIfAbsent的流程类似,这里主要分析下put的流程:

  1. 通过hash定位该key应该位于哪个Segment

  2. 调用Segment的put方法完成键值的插入

下面会解析Segment的put流程;

2.2.2 get流程

  @Override
  @Nullable
  public V get(@Nullable Object key) {
    if (key == null) {
      return null;
    }
    int hash = hash(key);
    return segmentFor(hash).get(key, hash);
  }

  @Nullable
  public V getIfPresent(Object key) {
    int hash = hash(checkNotNull(key));
    V value = segmentFor(hash).get(key, hash);
    if (value == null) {
      globalStatsCounter.recordMisses(1);
    } else {
      globalStatsCounter.recordHits(1);
    }
    return value;
  }

  @Nullable
  public V getOrDefault(@Nullable Object key, @Nullable V defaultValue) {
    V result = get(key);
    return (result != null) ? result : defaultValue;
  }
  1. 通过hash定位该key应该位于哪个Segment

  2. 调用Segment的get方法进行查询

下面会解析Segment的get流程;

2.2.3 getOrLoad流程

  V get(K key, CacheLoader 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);
  }
  1. 通过hash定位该key应该位于哪个Segment

  2. 调用Segment的get方法进行查询

下面会解析Segment的get流程;

2.2.4 refresh流程

void refresh(K key) {
  int hash = hash(checkNotNull(key));
  segmentFor(hash).refresh(key, hash, defaultLoader, false);
}

同样委托给Segment进行refresh;

3. Segment核心流程

3.1 put流程

Segment的put代码如下:

@Nullable
V put(K key, int hash, V value, boolean onlyIfAbsent) {
  lock();   //每个segment写操作都需要加锁
  try {
    long now = map.ticker.read();
    preWriteCleanup(now);      //put前的一些操作,1.垃圾回收的键值进行移除 2.过期的键值进行移除

    int newCount = this.count + 1;
    if (newCount > this.threshold) { // ensure capacity
      expand();
      newCount = this.count + 1;
    }

    AtomicReferenceArray> table = this.table;
    int index = hash & (table.length() - 1);
    ReferenceEntry first = table.get(index);

    // Look for an existing entry.
    for (ReferenceEntry 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 valueReference = e.getValueReference();
        V entryValue = valueReference.get();

        if (entryValue == null) {
          ++modCount;
          if (valueReference.isActive()) {
            enqueueNotification(
              key, hash, entryValue, valueReference.getWeight(), 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(e);
          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
          ++modCount;
          enqueueNotification(
            key, hash, entryValue, valueReference.getWeight(), RemovalCause.REPLACED);
          setValue(e, key, value, now);
          evictEntries(e);
          return entryValue;
        }
      }
    }

    // Create a new entry.
    ++modCount;
    ReferenceEntry newEntry = newEntry(key, hash, first);
    setValue(newEntry, key, value, now);
    table.set(index, newEntry);
    newCount = this.count + 1;
    this.count = newCount; // write-volatile
    evictEntries(newEntry);
    return null;
  } finally {
    unlock();
    postWriteCleanup();
  }
}

put方法主要包含流程有:

  1. put前需要完成:1)垃圾回收的键值进行移除 2)过期的键值进行移除

  2. 元素个数达到阈值threshold,调用expand方法进行扩容

  3. 查找table是否已存在,存在更新(onlyIfAbsent=true),不存在插入

  4. 调用evictEntries方法查看是否需要移除元素,移除accessTime最早的元素;

  5. 通知removalListener;

3.2 get流程

@Nullable
V get(Object key, int hash) {
  try {
    if (count != 0) { // read-volatile
      long now = map.ticker.read();
      ReferenceEntry e = getLiveEntry(key, hash, now);
      if (e == null) {
        return null;
      }

      V value = e.getValueReference().get();
      if (value != null) {
        recordRead(e, now);
        return scheduleRefresh(e, e.getKey(), hash, value, now, map.defaultLoader);
      }
      tryDrainReferenceQueues();
    }
    return null;
  } finally {
    postReadCleanup();
  }
}

@Nullable
ReferenceEntry getLiveEntry(Object key, int hash, long now) {
  ReferenceEntry e = getEntry(key, hash);
  if (e == null) {
    return null;
  } else if (map.isExpired(e, now)) {
    tryExpireEntries(now);
    return null;
  }
  return e;
}
  1. 查询元素是否存在,不存在返回null

  2. 校验该元素是否过期,如果过期,清理所有过期元素,然后返回null;

  3. 校验value值是否为null(垃圾回收),如果为null,批次清理垃圾回收的元素,返回null

  4. value不为null,重新设置accessTime为now

  5. 是否配置了刷新周期,达到刷新周期,调用刷新函数进行刷新,refresh流程阐述如下;

3.3 refresh流程

/**
     * Refreshes the value associated with {@code key}, unless another thread is already doing so.
     * Returns the newly refreshed value associated with {@code key} if it was refreshed inline, or
     * {@code null} if another thread is performing the refresh or if an error occurs during
     * refresh.
     */
@Nullable
V refresh(K key, int hash, CacheLoader loader, boolean checkTime) {
  final LoadingValueReference loadingValueReference =
    insertLoadingValueReference(key, hash, checkTime);
  if (loadingValueReference == null) {
    return null;
  }

  ListenableFuture 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 loadAsync(
  final K key,
  final int hash,
  final LoadingValueReference loadingValueReference,
  CacheLoader loader) {
  final ListenableFuture loadingFuture = loadingValueReference.loadFuture(key, loader);
  loadingFuture.addListener(
    new Runnable() {
      @Override
      public void run() {
        try {
          getAndRecordStats(key, hash, loadingValueReference, loadingFuture);
        } catch (Throwable t) {
          logger.log(Level.WARNING, "Exception thrown during refresh", t);
          loadingValueReference.setException(t);
        }
      }
    },
    directExecutor());
  return loadingFuture;
}
  1. 插入LoadingValueReference,对新值进行加载

  2. 调用loadAsync进行异步加载

  3. 设置加载的新值并返回

4. RemovalListener流程

4.1 RemovalListener接口

Guava Cache中的元素在移除的时候,支持自定义RemovalListener,完成对remove操作的监听,对应的监听接口为RemovalListener,定义如下:

/**
 * An object that can receive a notification when an entry is removed from a cache. The removal
 * resulting in notification could have occured to an entry being manually removed or replaced, or
 * due to eviction resulting from timed expiration, exceeding a maximum size, or garbage collection.
 *
 * 

An instance may be called concurrently by multiple threads to process different entries. * Implementations of this interface should avoid performing blocking calls or synchronizing on * shared resources. * * @param the most general type of keys this listener can listen for; for example {@code Object} * if any key is acceptable * @param the most general type of values this listener can listen for; for example * {@code Object} if any key is acceptable * @author Charles Fry * @since 10.0 */ @GwtCompatible public interface RemovalListener { /** * Notifies the listener that a removal occurred at some point in the past. * *

This does not always signify that the key is now absent from the cache, as it may have * already been re-added. */ // Technically should accept RemovalNotification, but because // RemovalNotification is guaranteed covariant, let's make users' lives simpler. void onRemoval(RemovalNotification notification); }

4.2 RemovalNotification对象

实现子类通过onRemoval方法完成对remove操作的监听,这里监听的对象为RemovalNotification:

/**
 * A notification of the removal of a single entry. The key and/or value may be null if they were
 * already garbage collected.
 *
 * 

Like other {@code Map.Entry} instances associated with {@code CacheBuilder}, this class holds * strong references to the key and value, regardless of the type of references the cache may be * using. * * @author Charles Fry * @since 10.0 */ @GwtCompatible public final class RemovalNotification extends SimpleImmutableEntry { private final RemovalCause cause; /** * Creates a new {@code RemovalNotification} for the given {@code key}/{@code value} pair, with * the given {@code cause} for the removal. The {@code key} and/or {@code value} may be * {@code null} if they were already garbage collected. * * @since 19.0 */ public static RemovalNotification create( @Nullable K key, @Nullable V value, RemovalCause cause) { return new RemovalNotification(key, value, cause); } private RemovalNotification(@Nullable K key, @Nullable V value, RemovalCause cause) { super(key, value); this.cause = checkNotNull(cause); } /** * Returns the cause for which the entry was removed. */ public RemovalCause getCause() { return cause; } /** * Returns {@code true} if there was an automatic removal due to eviction (the cause is neither * {@link RemovalCause#EXPLICIT} nor {@link RemovalCause#REPLACED}). */ public boolean wasEvicted() { return cause.wasEvicted(); } private static final long serialVersionUID = 0; }

父类SimpleImmutableEntry是一个不可变对象,定义如下:

/**
     * An Entry maintaining an immutable key and value.  This class
     * does not support method setValue.  This class may be
     * convenient in methods that return thread-safe snapshots of
     * key-value mappings.
     *
     * @since 1.6
     */
public static class SimpleImmutableEntry
        implements Entry, java.io.Serializable
    {
        private static final long serialVersionUID = 7138329143949025153L;

        private final K key;
        private final V value;

        /**
         * Creates an entry representing a mapping from the specified
         * key to the specified value.
         *
         * @param key the key represented by this entry
         * @param value the value represented by this entry
         */
        public SimpleImmutableEntry(K key, V value) {
            this.key   = key;
            this.value = value;
        }

        /**
         * Creates an entry representing the same mapping as the
         * specified entry.
         *
         * @param entry the entry to copy
         */
        public SimpleImmutableEntry(Entry entry) {
            this.key   = entry.getKey();
            this.value = entry.getValue();
        }

        /**
         * Returns the key corresponding to this entry.
         *
         * @return the key corresponding to this entry
         */
        public K getKey() {
            return key;
        }

        /**
         * Returns the value corresponding to this entry.
         *
         * @return the value corresponding to this entry
         */
        public V getValue() {
            return value;
        }

        /**
         * Replaces the value corresponding to this entry with the specified
         * value (optional operation).  This implementation simply throws
         * UnsupportedOperationException, as this class implements
         * an immutable map entry.
         *
         * @param value new value to be stored in this entry
         * @return (Does not return)
         * @throws UnsupportedOperationException always
         */
        public V setValue(V value) {
            throw new UnsupportedOperationException();
        }

        /**
         * Compares the specified object with this entry for equality.
         * Returns {@code true} if the given object is also a map entry and
         * the two entries represent the same mapping.  More formally, two
         * entries {@code e1} and {@code e2} represent the same mapping
         * if
         *   (e1.getKey()==null ?
         *    e2.getKey()==null :
         *    e1.getKey().equals(e2.getKey()))
         *   &&
         *   (e1.getValue()==null ?
         *    e2.getValue()==null :
         *    e1.getValue().equals(e2.getValue()))
* This ensures that the {@code equals} method works properly across * different implementations of the {@code Map.Entry} interface. * * @param o object to be compared for equality with this map entry * @return {@code true} if the specified object is equal to this map * entry * @see #hashCode */ public boolean equals(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry e = (Map.Entry)o; return eq(key, e.getKey()) && eq(value, e.getValue()); } /** * Returns the hash code value for this map entry. The hash code * of a map entry {@code e} is defined to be:
         *   (e.getKey()==null   ? 0 : e.getKey().hashCode()) ^
         *   (e.getValue()==null ? 0 : e.getValue().hashCode())
* This ensures that {@code e1.equals(e2)} implies that * {@code e1.hashCode()==e2.hashCode()} for any two Entries * {@code e1} and {@code e2}, as required by the general * contract of {@link Object#hashCode}. * * @return the hash code value for this map entry * @see #equals */ public int hashCode() { return (key == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode()); } /** * Returns a String representation of this map entry. This * implementation returns the string representation of this * entry's key followed by the equals character ("=") * followed by the string representation of this entry's value. * * @return a String representation of this map entry */ public String toString() { return key + "=" + value; } }

 同时,RemovalNotification还包含一个RemovalCause对象,标识每次remove操作的原因:

/**
 * The reason why a cached entry was removed.
 *
 * @author Charles Fry
 * @since 10.0
 */
@GwtCompatible
public enum RemovalCause {
  /**
   * The entry was manually removed by the user. This can result from the user invoking
   * {@link Cache#invalidate}, {@link Cache#invalidateAll(Iterable)}, {@link Cache#invalidateAll()},
   * {@link Map#remove}, {@link ConcurrentMap#remove}, or {@link Iterator#remove}.
   */
  EXPLICIT {
    @Override
    boolean wasEvicted() {
      return false;
    }
  },

  /**
   * The entry itself was not actually removed, but its value was replaced by the user. This can
   * result from the user invoking {@link Cache#put}, {@link LoadingCache#refresh}, {@link Map#put},
   * {@link Map#putAll}, {@link ConcurrentMap#replace(Object, Object)}, or
   * {@link ConcurrentMap#replace(Object, Object, Object)}.
   */
  REPLACED {
    @Override
    boolean wasEvicted() {
      return false;
    }
  },

  /**
   * The entry was removed automatically because its key or value was garbage-collected. This can
   * occur when using {@link CacheBuilder#weakKeys}, {@link CacheBuilder#weakValues}, or
   * {@link CacheBuilder#softValues}.
   */
  COLLECTED {
    @Override
    boolean wasEvicted() {
      return true;
    }
  },

  /**
   * The entry's expiration timestamp has passed. This can occur when using
   * {@link CacheBuilder#expireAfterWrite} or {@link CacheBuilder#expireAfterAccess}.
   */
  EXPIRED {
    @Override
    boolean wasEvicted() {
      return true;
    }
  },

  /**
   * The entry was evicted due to size constraints. This can occur when using
   * {@link CacheBuilder#maximumSize} or {@link CacheBuilder#maximumWeight}.
   */
  SIZE {
    @Override
    boolean wasEvicted() {
      return true;
    }
  };

  /**
   * Returns {@code true} if there was an automatic removal due to eviction (the cause is neither
   * {@link #EXPLICIT} nor {@link #REPLACED}).
   */
  abstract boolean wasEvicted();
}

4.3 RemovalListeners

RemovalListeners通过asynchronous方法将一个RemovalListener转变为一个可以异步执行的RemovalListener;

/**
 * A collection of common removal listeners.
 *
 * @author Charles Fry
 * @since 10.0
 */
@GwtIncompatible
public final class RemovalListeners {

  private RemovalListeners() {}

  /**
   * Returns a {@code RemovalListener} which processes all eviction notifications using
   * {@code executor}.
   *
   * @param listener the backing listener
   * @param executor the executor with which removal notifications are asynchronously executed
   */
  public static  RemovalListener asynchronous(
      final RemovalListener listener, final Executor executor) {
    checkNotNull(listener);
    checkNotNull(executor);
    return new RemovalListener() {
      @Override
      public void onRemoval(final RemovalNotification notification) {
        executor.execute(
            new Runnable() {
              @Override
              public void run() {
                listener.onRemoval(notification);
              }
            });
      }
    };
  }
}

5.  总结

至此,Guava Cache利用LocalCache类完成了对其属性的支持,内部也主要是依赖Segment类完成锁同步实现分段锁机制,允许多个线程并发更新操作;

在上述的核心接口实现中,包括了过期时间处理、过期回收策略、缓存加载机制、元素移除与通知机制、垃圾回收处理、用前后索引实现链表的基本操作处理、队列Queue基本操作等等,完美完成了对其声明属性的支持;

你可能感兴趣的:(guava,cache,框架,java,后端,缓存)