1. 程式人生 > 其它 >【優雅程式碼】15-guavaCache本地快取使用及原始碼解析

【優雅程式碼】15-guavaCache本地快取使用及原始碼解析

【優雅程式碼】15-guavaCache本地快取使用及原始碼解析

歡迎關注b站賬號/公眾號【六邊形戰士夏寧】,一個要把各項指標拉滿的男人。該文章已在github目錄收錄。
螢幕前的大帥比大漂亮如果有幫助到你的話請順手點個贊、加個收藏這對我真的很重要。別下次一定了,都不關注上哪下次一定。

1.背景

承接前一篇章的guava精選方法

2.cache

這一塊的功能設計真的很精巧,特別是佇列的設計

2.1使用

@SneakyThrows
public static void cache() {
    // 注意兩個如果一起用有時候會有bug
    Cache<Integer, Integer> accessBuild = CacheBuilder.newBuilder().expireAfterAccess(1, TimeUnit.SECONDS).build();
    Cache<Integer, Integer> writeBuild = CacheBuilder.newBuilder().expireAfterWrite(1, TimeUnit.SECONDS).build();

    accessBuild.put(1, 1);
    accessBuild.put(2, 2);
    writeBuild.put(1, 1);
    writeBuild.put(2, 2);
    // 輸出1
    System.out.println(accessBuild.getIfPresent(1));
    // 輸出1
    System.out.println(writeBuild.getIfPresent(1));
    Thread.sleep(500);
    // 輸出2
    System.out.println(accessBuild.getIfPresent(2));
    Thread.sleep(600);
    // 輸出null
    System.out.println(accessBuild.getIfPresent(1));
    // 輸出2
    System.out.println(accessBuild.getIfPresent(2));
    // 輸出null
    System.out.println(writeBuild.getIfPresent(1));
}

輸出如下

1
1
2
null
2
null

2.2核心原始碼詳解

  1. 構造方法
// 整體構造鏈相對簡單
// build方法
public <K1 extends K, V1 extends V> Cache<K1, V1> build() {
    checkWeightWithWeigher();
    checkNonLoadingCache();
    return new LocalCache.LocalManualCache<>(this);
  }
// 給expireAfterAccessNanos賦值失效時間
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;
  }
// 給expireAfterWriteNanos賦值失效時間
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;
  }
  1. put
// put方法,和老版本的ConcurrentHashMap一樣的設計模式,用segment桶
@Override
  public V put(K key, V value) {
    checkNotNull(key);
    checkNotNull(value);
    int hash = hash(key);
    return segmentFor(hash).put(key, hash, value, false);
  }

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.
    // 這裡判斷一下是不是已經有同樣的key了
    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, 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<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(newEntry);
    return null;
  } finally {
    unlock();
     // **********重點方法:呼叫監聽者********
    postWriteCleanup();
  }
}
  1. setValue
@GuardedBy("this")
void setValue(ReferenceEntry<K, V> entry, K key, V value, long now) {
    // 獲取這個包裝entry原先的值,如果原先這個key不存在,則獲取不到東西
  ValueReference<K, V> previous = entry.getValueReference();
  int weight = map.weigher.weigh(key, value);
  checkState(weight >= 0, "Weights must be non-negative");

  ValueReference<K, V> valueReference =
      map.valueStrength.referenceValue(this, entry, value, weight);
    // 將value寫入到entry包裝物件中
  entry.setValueReference(valueReference);
  // 核心方法
  recordWrite(entry, weight, now);
  previous.notifyNewValue(value);
}
@GuardedBy("this")
void recordWrite(ReferenceEntry<K, V> entry, int weight, long now) {
  // we are already under lock, so drain the recency queue immediately
  drainRecencyQueue();
  totalWeight += weight;

  if (map.recordsAccess()) {
    // 設定訪問時間
    entry.setAccessTime(now);
  }
  if (map.recordsWrite()) {
    // 設定寫時間
    entry.setWriteTime(now);
  }
  // **************重點方法***************這個地方將指標存了兩份佇列到末尾,因為快取時間是一致的,所以只要判斷佇列頭部就可以了
  accessQueue.add(entry);
  writeQueue.add(entry);
}
  1. WriteQueue與accessQueue
    這兩個用的實現類基本一致,這裡重寫了add方法,重寫的目的是如果key一樣的entry就進行重排而不是插入
@Override
    public boolean offer(ReferenceEntry<K, V> entry) {
      // unlink
      // 將entry的前一個和後一個進行互指
      connectWriteOrder(entry.getPreviousInWriteQueue(), entry.getNextInWriteQueue());

      // add to tail
      // entry和對頭互指,和隊尾互指,即新增到隊尾
      connectWriteOrder(head.getPreviousInWriteQueue(), entry);
      connectWriteOrder(entry, head);

      return true;
    }
    // 兩個物件進行互指
static <K, V> void connectWriteOrder(ReferenceEntry<K, V> previous, ReferenceEntry<K, V> next) {
    previous.setNextInWriteQueue(next);
    next.setPreviousInWriteQueue(previous);
  }
  1. preWriteCleanup
void preWriteCleanup(long now) {
      runLockedCleanup(now);
    }
 void runLockedCleanup(long now) {
      if (tryLock()) {
        try {
          drainReferenceQueues();
          // 核心方法繼續進入
          expireEntries(now); // calls drainRecencyQueue
          readCount.set(0);
        } finally {
          unlock();
        }
      }
    }
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();
        }
      }
    }
  1. 監聽者模式
void postWriteCleanup() {
    // 核心方法繼續進入
      runUnlockedCleanup();
    }

void runUnlockedCleanup() {
      // locked cleanup may generate notifications we can send unlocked
      if (!isHeldByCurrentThread()) {
        // 核心方法繼續進入
        map.processPendingNotifications();
      }
    }
void processPendingNotifications() {
    RemovalNotification<K, V> notification;
    // 前面所有涉及notify的操作都會進到相應的queue中,然後在該主方法中進行回撥
    while ((notification = removalNotificationQueue.poll()) != null) {
      try {
        // 核心流程,只要實現removalListener,通過構造方法傳進來,然後這裡就會同步呼叫實現的回撥方法
        // PS第一遍看原始碼一度卡在這個位置,不知道這玩意兒就一個通知機制怎麼就移除元素了
        removalListener.onRemoval(notification);
      } catch (Throwable e) {
        logger.log(Level.WARNING, "Exception thrown by removal listener", e);
      }
    }
  }