guava二级缓存原理深入理解

时间:5年前   阅读:4949

1、CacheBuilder 是如何创建的?

@GwtCompatible(emulated = true)

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() {

        @Override

        public void recordHits(int count) {}

        @Override

        public void recordMisses(int count) {}

        @Override

        public void recordLoadSuccess(long loadTime) {}

        @Override

        public void recordLoadException(long loadTime) {}

        @Override

        public void recordEviction() {}

        @Override

        public CacheStats snapshot() {

          return EMPTY_STATS;

        }

      });

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

    }

  };

  enum NullListener implements RemovalListener<Object, Object> {

    INSTANCE;

    @Override

    public void onRemoval(RemovalNotification<Object, Object> notification) {}

  }

  enum OneWeigher implements Weigher<Object, Object> {

    INSTANCE;

    @Override

    public int weigh(Object key, Object value) {

      return 1;

    }

  }

  static final Ticker NULL_TICKER = new Ticker() {

    @Override

    public long read() {

      return 0;

    }

  };

  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;

  Strength valueStrength;

  long expireAfterWriteNanos = UNSET_INT;

  long expireAfterAccessNanos = UNSET_INT;

  long refreshNanos = UNSET_INT;

  Equivalence<Object> keyEquivalence;

  Equivalence<Object> valueEquivalence;

  RemovalListener<? super K, ? super V> removalListener;

  Ticker ticker;

  Supplier<? extends StatsCounter> statsCounterSupplier = NULL_STATS_COUNTER;

  // TODO(fry): make constructor private and update tests to use newBuilder

  CacheBuilder() {}

  /**

   * Constructs a new {@code CacheBuilder} instance with default settings, including strong keys,

   * strong values, and no automatic eviction of any kind.

   */

  public static CacheBuilder<Object, Object> newBuilder() {

    return new CacheBuilder<Object, Object>();

  }

  /**

   * Sets the minimum total size for the internal hash tables. For example, if the initial capacity

   * is {@code 60}, and the concurrency level is {@code 8}, then eight segments are created, each

   * having a hash table of size eight. Providing a large enough estimate at construction time

   * avoids the need for expensive resizing operations later, but setting this value unnecessarily

   * high wastes memory.

   *

   * @throws IllegalArgumentException if {@code initialCapacity} is negative

   * @throws IllegalStateException if an initial capacity was already set

   */

  public CacheBuilder<K, V> initialCapacity(int initialCapacity) {

    checkState(this.initialCapacity == UNSET_INT, "initial capacity was already set to %s",

        this.initialCapacity);

    checkArgument(initialCapacity >= 0);

    this.initialCapacity = initialCapacity;

    return this;

  }

  int getInitialCapacity() {

    return (initialCapacity == UNSET_INT) ? DEFAULT_INITIAL_CAPACITY : initialCapacity;

  }

  /**

   * Guides the allowed concurrency among update operations. Used as a hint for internal sizing. The

   * table is internally partitioned to try to permit the indicated number of concurrent updates

   * without contention. Because assignment of entries to these partitions is not necessarily

   * uniform, the actual concurrency observed may vary. Ideally, you should choose a value to

   * accommodate as many threads as will ever concurrently modify the table. Using a significantly

   * higher value than you need can waste space and time, and a significantly lower value can lead

   * to thread contention. But overestimates and underestimates within an order of magnitude do not

   * usually have much noticeable impact. A value of one permits only one thread to modify the cache

   * at a time, but since read operations and cache loading computations can proceed concurrently,

   * this still yields higher concurrency than full synchronization.

   *

   * <p> Defaults to 4. <b>Note:</b>The default may change in the future. If you care about this

   * value, you should always choose it explicitly.

   *

   * <p>The current implementation uses the concurrency level to create a fixed number of hashtable

   * segments, each governed by its own write lock. The segment lock is taken once for each explicit

   * write, and twice for each cache loading computation (once prior to loading the new value,

   * and once after loading completes). Much internal cache management is performed at the segment

   * granularity. For example, access queues and write queues are kept per segment when they are

   * required by the selected eviction algorithm. As such, when writing unit tests it is not

   * uncommon to specify {@code concurrencyLevel(1)} in order to achieve more deterministic eviction

   * behavior.

   *

   * <p>Note that future implementations may abandon segment locking in favor of more advanced

   * concurrency controls.

   *

   * @throws IllegalArgumentException if {@code concurrencyLevel} is nonpositive

   * @throws IllegalStateException if a concurrency level was already set

   */

  public CacheBuilder<K, V> concurrencyLevel(int concurrencyLevel) {

    checkState(this.concurrencyLevel == UNSET_INT, "concurrency level was already set to %s",

        this.concurrencyLevel);

    checkArgument(concurrencyLevel > 0);

    this.concurrencyLevel = concurrencyLevel;

    return this;

  }

  int getConcurrencyLevel() {

    return (concurrencyLevel == UNSET_INT) ? DEFAULT_CONCURRENCY_LEVEL : concurrencyLevel;

  }

  /**

   * Specifies the maximum number of entries the cache may contain. Note that the cache <b>may evict

   * an entry before this limit is exceeded</b>. As the cache size grows close to the maximum, the

   * cache evicts entries that are less likely to be used again. For example, the cache may evict an

   * entry because it hasn't been used recently or very often.

   *

   * <p>When {@code size} is zero, elements will be evicted immediately after being loaded into the

   * cache. This can be useful in testing, or to disable caching temporarily without a code change.

   *

   * <p>This feature cannot be used in conjunction with {@link #maximumWeight}.

   *

   * @param size the maximum size of the cache

   * @throws IllegalArgumentException if {@code size} is negative

   * @throws IllegalStateException if a maximum size or weight was already set

   */

  public CacheBuilder<K, V> maximumSize(long size) {

    checkState(this.maximumSize == UNSET_INT, "maximum size was already set to %s",

        this.maximumSize);

    checkState(this.maximumWeight == UNSET_INT, "maximum weight was already set to %s",

        this.maximumWeight);

    checkState(this.weigher == null, "maximum size can not be combined with weigher");

    checkArgument(size >= 0, "maximum size must not be negative");

    this.maximumSize = size;

    return this;

  }

  /**

   * Specifies the maximum weight of entries the cache may contain. Weight is determined using the

   * {@link Weigher} specified with {@link #weigher}, and use of this method requires a

   * corresponding call to {@link #weigher} prior to calling {@link #build}.

   *

   * <p>Note that the cache <b>may evict an entry before this limit is exceeded</b>. As the cache

   * size grows close to the maximum, the cache evicts entries that are less likely to be used

   * again. For example, the cache may evict an entry because it hasn't been used recently or very

   * often.

   *

   * <p>When {@code weight} is zero, elements will be evicted immediately after being loaded into

   * cache. This can be useful in testing, or to disable caching temporarily without a code

   * change.

   *

   * <p>Note that weight is only used to determine whether the cache is over capacity; it has no

   * effect on selecting which entry should be evicted next.

   *

   * <p>This feature cannot be used in conjunction with {@link #maximumSize}.

   *

   * @param weight the maximum total weight of entries the cache may contain

   * @throws IllegalArgumentException if {@code weight} is negative

   * @throws IllegalStateException if a maximum weight or size was already set

   * @since 11.0

   */

  @GwtIncompatible("To be supported")

  public CacheBuilder<K, V> maximumWeight(long weight) {

    checkState(this.maximumWeight == UNSET_INT, "maximum weight was already set to %s",

        this.maximumWeight);

    checkState(this.maximumSize == UNSET_INT, "maximum size was already set to %s",

        this.maximumSize);

    this.maximumWeight = weight;

    checkArgument(weight >= 0, "maximum weight must not be negative");

    return this;

  }

  /**

   * Specifies the weigher to use in determining the weight of entries. Entry weight is taken

   * into consideration by {@link #maximumWeight(long)} when determining which entries to evict, and

   * use of this method requires a corresponding call to {@link #maximumWeight(long)} prior to

   * calling {@link #build}. Weights are measured and recorded when entries are inserted into the

   * cache, and are thus effectively static during the lifetime of a cache entry.

   *

   * <p>When the weight of an entry is zero it will not be considered for size-based eviction

   * (though it still may be evicted by other means).

   *

   * <p><b>Important note:</b> Instead of returning <em>this</em> as a {@code CacheBuilder}

   * instance, this method returns {@code CacheBuilder<K1, V1>}. From this point on, either the

   * original reference or the returned reference may be used to complete configuration and build

   * the cache, but only the "generic" one is type-safe. That is, it will properly prevent you from

   * building caches whose key or value types are incompatible with the types accepted by the

   * weigher already provided; the {@code CacheBuilder} type cannot do this. For best results,

   * simply use the standard method-chaining idiom, as illustrated in the documentation at top,

   * configuring a {@code CacheBuilder} and building your {@link Cache} all in a single statement.

   *

   * <p><b>Warning:</b> if you ignore the above advice, and use this {@code CacheBuilder} to build

   * a cache whose key or value type is incompatible with the weigher, you will likely experience

   * a {@link ClassCastException} at some <i>undefined</i> point in the future.

   *

   * @param weigher the weigher to use in calculating the weight of cache entries

   * @throws IllegalArgumentException if {@code size} is negative

   * @throws IllegalStateException if a maximum size was already set

   * @since 11.0

   */

  @GwtIncompatible("To be supported")

  public <K1 extends K, V1 extends V> CacheBuilder<K1, V1> weigher(

      Weigher<? super K1, ? super V1> weigher) {

    checkState(this.weigher == null);

    if (strictParsing) {

      checkState(this.maximumSize == UNSET_INT, "weigher can not be combined with maximum size",

          this.maximumSize);

    }

    // safely limiting the kinds of caches this can produce

    @SuppressWarnings("unchecked")

    CacheBuilder<K1, V1> me = (CacheBuilder<K1, V1>) this;

    me.weigher = checkNotNull(weigher);

    return me;

  }

  // Make a safe contravariant cast now so we don't have to do it over and over.

  @SuppressWarnings("unchecked")

  <K1 extends K, V1 extends V> Weigher<K1, V1> getWeigher() {

    return (Weigher<K1, V1>) MoreObjects.firstNonNull(weigher, OneWeigher.INSTANCE);

  }

  /**

   * Specifies that each entry should be automatically removed from the cache once a fixed duration

   * has elapsed after the entry's creation, or the most recent replacement of its value.

   *

   * <p>When {@code duration} is zero, this method hands off to

   * {@link #maximumSize(long) maximumSize}{@code (0)}, ignoring any otherwise-specificed maximum

   * size or weight. This can be useful in testing, or to disable caching temporarily without a code

   * change.

   *

   * <p>Expired entries may be counted in {@link Cache#size}, but will never be visible to read or

   * write operations. Expired entries are cleaned up as part of the routine maintenance described

   * in the class javadoc.

   *

   * @param duration the length of time after an entry is created that it should be automatically

   *     removed

   * @param unit the unit that {@code duration} is expressed in

   * @throws IllegalArgumentException if {@code duration} is negative

   * @throws IllegalStateException if the time to live or time to idle was already set

   */

  public CacheBuilder<K, V> expireAfterWrite(long duration, TimeUnit unit) {

    checkState(expireAfterWriteNanos == UNSET_INT, "expireAfterWrite was already set to %s ns",

        expireAfterWriteNanos);

    checkArgument(duration >= 0, "duration cannot be negative: %s %s", duration, unit);

    this.expireAfterWriteNanos = unit.toNanos(duration);

    return this;

  }

  long getExpireAfterWriteNanos() {

    return (expireAfterWriteNanos == UNSET_INT) ? DEFAULT_EXPIRATION_NANOS : expireAfterWriteNanos;

  }

  /**

   * Specifies that each entry should be automatically removed from the cache once a fixed duration

   * has elapsed after the entry's creation, the most recent replacement of its value, or its last

   * access. Access time is reset by all cache read and write operations (including

   * {@code Cache.asMap().get(Object)} and {@code Cache.asMap().put(K, V)}), but not by operations

   * on the collection-views of {@link Cache#asMap}.

   *

   * <p>When {@code duration} is zero, this method hands off to

   * {@link #maximumSize(long) maximumSize}{@code (0)}, ignoring any otherwise-specificed maximum

   * size or weight. This can be useful in testing, or to disable caching temporarily without a code

   * change.

   *

   * <p>Expired entries may be counted in {@link Cache#size}, but will never be visible to read or

   * write operations. Expired entries are cleaned up as part of the routine maintenance described

   * in the class javadoc.

   *

   * @param duration the length of time after an entry is last accessed that it should be

   *     automatically removed

   * @param unit the unit that {@code duration} is expressed in

   * @throws IllegalArgumentException if {@code duration} is negative

   * @throws IllegalStateException if the time to idle or time to live was already set

   */

  public CacheBuilder<K, V> expireAfterAccess(long duration, TimeUnit unit) {

    checkState(expireAfterAccessNanos == UNSET_INT, "expireAfterAccess was already set to %s ns",

        expireAfterAccessNanos);

    checkArgument(duration >= 0, "duration cannot be negative: %s %s", duration, unit);

    this.expireAfterAccessNanos = unit.toNanos(duration);

    return this;

  }

  long getExpireAfterAccessNanos() {

    return (expireAfterAccessNanos == UNSET_INT)

        ? DEFAULT_EXPIRATION_NANOS : expireAfterAccessNanos;

  }

  /**

   * Specifies that active entries are eligible for automatic refresh once a fixed duration has

   * elapsed after the entry's creation, or the most recent replacement of its value. The semantics

   * of refreshes are specified in {@link LoadingCache#refresh}, and are performed by calling

   * {@link CacheLoader#reload}.

   *

   * <p>As the default implementation of {@link CacheLoader#reload} is synchronous, it is

   * recommended that users of this method override {@link CacheLoader#reload} with an asynchronous

   * implementation; otherwise refreshes will be performed during unrelated cache read and write

   * operations.

   *

   * <p>Currently automatic refreshes are performed when the first stale request for an entry

   * occurs. The request triggering refresh will make a blocking call to {@link CacheLoader#reload}

   * and immediately return the new value if the returned future is complete, and the old value

   * otherwise.

   *

   * <p><b>Note:</b> <i>all exceptions thrown during refresh will be logged and then swallowed</i>.

   *

   * @param duration the length of time after an entry is created that it should be considered

   *     stale, and thus eligible for refresh

   * @param unit the unit that {@code duration} is expressed in

   * @throws IllegalArgumentException if {@code duration} is negative

   * @throws IllegalStateException if the refresh interval was already set

   * @since 11.0

   */

  @Beta

  @GwtIncompatible("To be supported (synchronously).")

  public CacheBuilder<K, V> refreshAfterWrite(long duration, TimeUnit unit) {

    checkNotNull(unit);

    checkState(refreshNanos == UNSET_INT, "refresh was already set to %s ns", refreshNanos);

    checkArgument(duration > 0, "duration must be positive: %s %s", duration, unit);

    this.refreshNanos = unit.toNanos(duration);

    return this;

  }

  long getRefreshNanos() {

    return (refreshNanos == UNSET_INT) ? DEFAULT_REFRESH_NANOS : refreshNanos;

  }

  /**

   * Specifies a nanosecond-precision time source for use in determining when entries should be

   * expired. By default, {@link System#nanoTime} is used.

   *

   * <p>The primary intent of this method is to facilitate testing of caches which have been

   * configured with {@link #expireAfterWrite} or {@link #expireAfterAccess}.

   *

   * @throws IllegalStateException if a ticker was already set

   */

  public CacheBuilder<K, V> ticker(Ticker ticker) {

    checkState(this.ticker == null);

    this.ticker = checkNotNull(ticker);

    return this;

  }

  Ticker getTicker(boolean recordsTime) {

    if (ticker != null) {

      return ticker;

    }

    return recordsTime ? Ticker.systemTicker() : NULL_TICKER;

  }

  /**

   * Specifies a listener instance that caches should notify each time an entry is removed for any

   * {@linkplain RemovalCause reason}. Each cache created by this builder will invoke this listener

   * as part of the routine maintenance described in the class documentation above.

   *

   * <p><b>Warning:</b> after invoking this method, do not continue to use <i>this</i> cache

   * builder reference; instead use the reference this method <i>returns</i>. At runtime, these

   * point to the same instance, but only the returned reference has the correct generic type

   * information so as to ensure type safety. For best results, use the standard method-chaining

   * idiom illustrated in the class documentation above, configuring a builder and building your

   * cache in a single statement. Failure to heed this advice can result in a {@link

   * ClassCastException} being thrown by a cache operation at some <i>undefined</i> point in the

   * future.

   *

   * <p><b>Warning:</b> any exception thrown by {@code listener} will <i>not</i> be propagated to

   * the {@code Cache} user, only logged via a {@link Logger}.

   *

   * @return the cache builder reference that should be used instead of {@code this} for any

   *     remaining configuration and cache building

   * @throws IllegalStateException if a removal listener was already set

   */

  @CheckReturnValue

  public <K1 extends K, V1 extends V> CacheBuilder<K1, V1> removalListener(

      RemovalListener<? super K1, ? super V1> listener) {

    checkState(this.removalListener == null);

    // safely limiting the kinds of caches this can produce

    @SuppressWarnings("unchecked")

    CacheBuilder<K1, V1> me = (CacheBuilder<K1, V1>) this;

    me.removalListener = checkNotNull(listener);

    return me;

  }

  // Make a safe contravariant cast now so we don't have to do it over and over.

  @SuppressWarnings("unchecked")

  <K1 extends K, V1 extends V> RemovalListener<K1, V1> getRemovalListener() {

    return (RemovalListener<K1, V1>)

        MoreObjects.firstNonNull(removalListener, NullListener.INSTANCE);

  }

  /**

   * Enable the accumulation of {@link CacheStats} during the operation of the cache. Without this

   * {@link Cache#stats} will return zero for all statistics. Note that recording stats requires

   * bookkeeping to be performed with each operation, and thus imposes a performance penalty on

   * cache operation.

   *

   * @since 12.0 (previously, stats collection was automatic)

   */

  public CacheBuilder<K, V> recordStats() {

    statsCounterSupplier = CACHE_STATS_COUNTER;

    return this;

  }

  

  boolean isRecordingStats() {

    return statsCounterSupplier == CACHE_STATS_COUNTER;

  }

  Supplier<? extends StatsCounter> getStatsCounterSupplier() {

    return statsCounterSupplier;

  }

  /**

   * 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.

   *

   * <p>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 <K1 extends K, V1 extends V> LoadingCache<K1, V1> build(

      CacheLoader<? super K1, V1> loader) {

    checkWeightWithWeigher();

    return new LocalCache.LocalLoadingCache<K1, V1>(this, loader);

  }

  /**

   * Builds a cache which does not automatically load values when keys are requested.

   *

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

   * {@code CacheLoader}.

   *

   * <p>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 <K1 extends K, V1 extends V> Cache<K1, V1> build() {

    checkWeightWithWeigher();

    checkNonLoadingCache();

    return new LocalCache.LocalManualCache<K1, V1>(this);

  }

}

  如上,使用建造者模式创建 LoadingCache<K, V> 缓存; 设置好 最大值,过期时间等参数;

2、如何获取一个guava缓存?

  其实就是一个get方法而已! stringDbCacheContainer.get(key);

    // com.google.common.cache.LocalCache

    // LoadingCache methods

    @Override

    public V get(K key) throws ExecutionException {

        // 两种数据来源,一是直接获取,二是调用 load() 方法加载数据

      return localCache.getOrLoad(key);

    }

    // com.google.common.cache.LocalCache

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

    // 还记得 ConcurrentHashMap 吗? 先定位segment, 再定位 entry

    return segmentFor(hash).get(key, hash, loader);

  }

  Segment<K, V> segmentFor(int hash) {

    // TODO(fry): Lazily create segments?

    return segments[(hash >>> segmentShift) & segmentMask];

  }

    // 核心取数逻辑在此get 中

    // loading

    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) {

            // 如果存在值,则依据 ticker 进行判断是否过期,从而直接返回值,具体过期逻辑稍后再说

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

      }

    }

    // static class Segment<K, V> extends ReentrantLock

    // 整个 Segment 继承了 ReentrantLock, 所以 LocalCache 的锁是依赖于 ReentrantLock 实现的

    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;

        AtomicReferenceArray<ReferenceEntry<K, V>> table = this.table;

        int index = hash & (table.length() - 1);

        ReferenceEntry<K, V> first = table.get(index);

        // 处理 hash 碰撞时的链表查询

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

              if (value == null) {

                enqueueNotification(entryKey, hash, valueReference, RemovalCause.COLLECTED);

              } else if (map.isExpired(e, now)) {

                // This is a duplicate check, as preWriteCleanup already purged expired

                // entries, but let's accomodate an incorrect expiration queue.

                enqueueNotification(entryKey, hash, valueReference, RemovalCause.EXPIRED);

              } else {

                recordLockedRead(e, now);

                statsCounter.recordHits(1);

                // we were concurrent with loading; don't consider refresh

                return value;

              }

              // immediately reuse invalid entries

              writeQueue.remove(e);

              accessQueue.remove(e);

              this.count = newCount; // write-volatile

            }

            break;

          }

        }

        // 如果是第一次加载,则先创建 Entry, 进入 load() 逻辑

        if (createNewEntry) {

          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 {

        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.

          // 同步加载数据源值, 从 loader 中处理

          synchronized (e) {

            return loadSync(key, hash, loadingValueReference, loader);

          }

        } finally {

            // 记录未命中计数,默认为空

          statsCounter.recordMisses(1);

        }

      } else {

        // The entry already exists. Wait for loading.

        // 如果有线程正在更新缓存,则等待结果即可,具体实现就是调用 Future.get() 

        return waitForLoadingValue(e, key, valueReference);

      }

    }

    // 加载原始值

    // at most one of loadSync.loadAsync may be called for any given LoadingValueReference

    V loadSync(K key, int hash, LoadingValueReference<K, V> loadingValueReference,

        CacheLoader<? super K, V> loader) throws ExecutionException {

        // loadingValueReference中保存了回调引用,加载原始值

      ListenableFuture<V> loadingFuture = loadingValueReference.loadFuture(key, loader);

      // 存储数据入缓存,以便下次使用

      return getAndRecordStats(key, hash, loadingValueReference, loadingFuture);

    }

    // 从 loader 中加载数据, 

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

        }

        // 否则一般为无过期时间的数据进行 reload, 如果 reload() 的结果为空,则直接返回

        // 须重写 reload() 实现

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

      }

    }

    // com.google.common.util.concurrent.Uninterruptibles

    /**

     * Waits uninterruptibly for {@code newValue} to be loaded, and then records loading stats.

     */

    V getAndRecordStats(K key, int hash, LoadingValueReference<K, V> loadingValueReference,

        ListenableFuture<V> newValue) throws ExecutionException {

      V value = null;

      try {

        // 同步等待加载结果,注意,此处返回值不允许为null, 否则将报异常,这可能是为了规避缓存攻击漏洞吧

        value = getUninterruptibly(newValue);

        if (value == null) {

          throw new InvalidCacheLoadException("CacheLoader returned null for key " + key + ".");

        }

        // 加载成功记录,此处扩展点,默认为空

        statsCounter.recordLoadSuccess(loadingValueReference.elapsedNanos());

        // 最后将值存入缓存容器中,返回(论hash的重要性)

        storeLoadedValue(key, hash, loadingValueReference, value);

        return value;

      } finally {

        if (value == null) {

          statsCounter.recordLoadException(loadingValueReference.elapsedNanos());

          removeLoadingValue(key, hash, loadingValueReference);

        }

      }

    }

    

  /**

   * Invokes {@code future.}{@link Future#get() get()} uninterruptibly.

   * To get uninterruptibility and remove checked exceptions, see

   * {@link Futures#getUnchecked}.

   *

   * <p>If instead, you wish to treat {@link InterruptedException} uniformly

   * with other exceptions, see {@link Futures#get(Future, Class) Futures.get}

   * or {@link Futures#makeChecked}.

   *

   * @throws ExecutionException if the computation threw an exception

   * @throws CancellationException if the computation was cancelled

   */

  public static <V> V getUninterruptibly(Future<V> future)

      throws ExecutionException {

    boolean interrupted = false;

    try {

      while (true) {

        try {

          return future.get();

        } catch (InterruptedException e) {

          interrupted = true;

        }

      }

    } finally {

      if (interrupted) {

        Thread.currentThread().interrupt();

      }

    }

  }

  如上,就是获取一个缓存的过程。总结下来就是:

  1. 先使用hash定位到 segment中,然后尝试直接到 map中获取结果;

  2. 如果没有找到或者已过期,则调用客户端的load()方法加载原始数据;

  3. 将结果存入 segment.map 中,本地缓存生效;

  4. 记录命中情况,读取计数;

3、如何处理过期?

   其实刚刚我们在看get()方法时,就看到了一些端倪。

  要确认两点: 1. 是否有创建异步清理线程进行过期数据清理? 2. 清理过程中,原始数据如何自处?

  其实guava的清理时机是在加载数据之前进行的!

    // com.google.common.cache.LocalCache

    // static class Segment<K, V> extends ReentrantLock

    // 整个 Segment 继承了 ReentrantLock, 所以 LocalCache 的锁是依赖于 ReentrantLock 实现的

    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;

        AtomicReferenceArray<ReferenceEntry<K, V>> table = this.table;

        int index = hash & (table.length() - 1);

        ReferenceEntry<K, V> first = table.get(index);

        // 处理 hash 碰撞时的链表查询

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

              if (value == null) {

                enqueueNotification(entryKey, hash, valueReference, RemovalCause.COLLECTED);

              } else if (map.isExpired(e, now)) {

                // This is a duplicate check, as preWriteCleanup already purged expired

                // entries, but let's accomodate an incorrect expiration queue.

                enqueueNotification(entryKey, hash, valueReference, RemovalCause.EXPIRED);

              } else {

                recordLockedRead(e, now);

                statsCounter.recordHits(1);

                // we were concurrent with loading; don't consider refresh

                return value;

              }

              // immediately reuse invalid entries

              writeQueue.remove(e);

              accessQueue.remove(e);

              this.count = newCount; // write-volatile

            }

            break;

          }

        }

        // 如果是第一次加载,则先创建 Entry, 进入 load() 逻辑

        if (createNewEntry) {

          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 {

        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.

          // 同步加载数据源值, 从 loader 中处理

          synchronized (e) {

            return loadSync(key, hash, loadingValueReference, loader);

          }

        } finally {

            // 记录未命中计数,默认为空

          statsCounter.recordMisses(1);

        }

      } else {

        // The entry already exists. Wait for loading.

        return waitForLoadingValue(e, key, valueReference);

      }

    }

    // 我们来细看下 preWriteCleanup(now);  是如何清理过期数据的

    /**

     * Performs routine cleanup prior to executing a write. This should be called every time a

     * write thread acquires the segment lock, immediately after acquiring the lock.

     *

     * <p>Post-condition: expireEntries has been run.

     */

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

        }

      }

    }

    /**

     * Drain the key and value reference queues, cleaning up internal entries containing garbage

     * collected keys or values.

     */

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

        }

      }

    }

    @Override

    public ReferenceEntry<K, V> peek() {

      ReferenceEntry<K, V> next = head.getNextInAccessQueue();

      return (next == head) ? null : next;

    }

    // 清理指定类型的元素,如 过期元素

    @GuardedBy("this")

    boolean removeEntry(ReferenceEntry<K, V> entry, int hash, RemovalCause cause) {

      int 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()) {

        if (e == entry) {

          ++modCount;

          // 调用 removeValueFromChain, 清理具体元素

          ReferenceEntry<K, V> newFirst = removeValueFromChain(

              first, e, e.getKey(), hash, e.getValueReference(), cause);

          newCount = this.count - 1;

          table.set(index, newFirst);

          this.count = newCount; // write-volatile

          return true;

        }

      }

      return false;

    }

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

      }

    }

    @GuardedBy("this")

    @Nullable

    ReferenceEntry<K, V> removeEntryFromChain(ReferenceEntry<K, V> first,

        ReferenceEntry<K, V> entry) {

      int newCount = count;

      // 普通情况,则直接返回 next 元素链即可

      // 针对有first != entry 的情况,则依次将 first 移动到队尾,然后跳到下一个元素返回

      ReferenceEntry<K, V> newFirst = entry.getNext();

      for (ReferenceEntry<K, V> e = first; e != entry; e = e.getNext()) {

        // 将first链表倒转到 newFirst 尾部

        ReferenceEntry<K, V> next = copyEntry(e, newFirst);

        if (next != null) {

          newFirst = next;

        } else {

          removeCollectedEntry(e);

          newCount--;

        }

      }

      this.count = newCount;

      return newFirst;

    }

    

  到此,我们就完整的看到了一个 key 的过期处理流程了。总结就是:

  1. 在读取的时候,触发清理操作;

  2. 使用 ReentrantLock 来进行线程安全的更新;

  3. 读取计数器清零,元素数量减少;

  3. 怎样主动放入一个缓存?

  这个和普通的map的put方法一样,简单看下即可!

    // com.google.common.cache.LocalCache$LocalManualCache

    @Override

    public void put(K key, V value) {

      localCache.put(key, value);

    }

    

    // com.google.common.cache.LocalCache

  @Override

  public V put(K key, V value) {

    checkNotNull(key);

    checkNotNull(value);

    int hash = hash(key);

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

  }

  

    // com.google.common.cache.LocalCache$Segment

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

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

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

            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

              ++modCount;

              enqueueNotification(key, hash, valueReference, RemovalCause.REPLACED);

              setValue(e, key, value, now);

              evictEntries();

              return entryValue;

            }

          }

        }

        // Create a new entry.

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

      }

    }

  就这样,基于guava的二级缓存功能就搞定了。

本站声明:网站内容来源于网络,如有侵权,请联系我们https://www.qiquanji.com,我们将及时处理。

微信扫码关注

更新实时通知

上一篇:js window.location

下一篇:双十一的背后反映了什么?

网友评论

请先 登录 再评论,若不是会员请先 注册