【優(yōu)雅代碼】15-guavaCache本地緩存使用及源碼解析

【優(yōu)雅代碼】15-guavaCache本地緩存使用及源碼解析

歡迎關(guān)注b站賬號(hào)/公眾號(hào)【六邊形戰(zhàn)士夏寧】熊痴,一個(gè)要把各項(xiàng)指標(biāo)拉滿(mǎn)的男人。該文章已在github目錄收錄。
屏幕前的大帥比大漂亮如果有幫助到你的話(huà)請(qǐng)順手點(diǎn)個(gè)贊敏弃、加個(gè)收藏這對(duì)我真的很重要笛辟。別下次一定了,都不關(guān)注上哪下次一定宫患。

1.背景

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

2.cache

這一塊的功能設(shè)計(jì)真的很精巧刊懈,特別是隊(duì)列的設(shè)計(jì)

2.1使用

@SneakyThrows
public static void cache() {
    // 注意兩個(gè)如果一起用有時(shí)候會(huì)有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. 構(gòu)造方法
// 整體構(gòu)造鏈相對(duì)簡(jiǎn)單
// build方法
public <K1 extends K, V1 extends V> Cache<K1, V1> build() {
    checkWeightWithWeigher();
    checkNonLoadingCache();
    return new LocalCache.LocalManualCache<>(this);
  }
// 給expireAfterAccessNanos賦值失效時(shí)間
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賦值失效時(shí)間
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一樣的設(shè)計(jì)模式娃闲,用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();
    // **********重點(diǎn)方法:清除過(guò)期內(nèi)容********
    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.
    // 這里判斷一下是不是已經(jīng)有同樣的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);
    // **********重點(diǎn)方法:賦值********
    setValue(newEntry, key, value, now);
    table.set(index, newEntry);
    newCount = this.count + 1;
    this.count = newCount; // write-volatile
    evictEntries(newEntry);
    return null;
  } finally {
    unlock();
     // **********重點(diǎn)方法:調(diào)用監(jiān)聽(tīng)者********
    postWriteCleanup();
  }
}
  1. setValue
@GuardedBy("this")
void setValue(ReferenceEntry<K, V> entry, K key, V value, long now) {
    // 獲取這個(gè)包裝entry原先的值虚汛,如果原先這個(gè)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寫(xiě)入到entry包裝對(duì)象中
  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()) {
    // 設(shè)置訪問(wèn)時(shí)間
    entry.setAccessTime(now);
  }
  if (map.recordsWrite()) {
    // 設(shè)置寫(xiě)時(shí)間
    entry.setWriteTime(now);
  }
  // **************重點(diǎn)方法***************這個(gè)地方將指針存了兩份隊(duì)列到末尾皇帮,因?yàn)榫彺鏁r(shí)間是一致的卷哩,所以只要判斷隊(duì)列頭部就可以了
  accessQueue.add(entry);
  writeQueue.add(entry);
}
  1. WriteQueue與accessQueue
    這兩個(gè)用的實(shí)現(xiàn)類(lèi)基本一致,這里重寫(xiě)了add方法属拾,重寫(xiě)的目的是如果key一樣的entry就進(jìn)行重排而不是插入
@Override
    public boolean offer(ReferenceEntry<K, V> entry) {
      // unlink
      // 將entry的前一個(gè)和后一個(gè)進(jìn)行互指
      connectWriteOrder(entry.getPreviousInWriteQueue(), entry.getNextInWriteQueue());

      // add to tail
      // entry和對(duì)頭互指将谊,和隊(duì)尾互指冷溶,即添加到隊(duì)尾
      connectWriteOrder(head.getPreviousInWriteQueue(), entry);
      connectWriteOrder(entry, head);

      return true;
    }
    // 兩個(gè)對(duì)象進(jìn)行互指
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();
          // 核心方法繼續(xù)進(jìn)入
          expireEntries(now); // calls drainRecencyQueue
          readCount.set(0);
        } finally {
          unlock();
        }
      }
    }
void expireEntries(long now) {
      drainRecencyQueue();

      ReferenceEntry<K, V> e;
      // 就兩個(gè)隊(duì)列不停的判斷頭節(jié)點(diǎn)是不是失效
      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. 監(jiān)聽(tīng)者模式
void postWriteCleanup() {
    // 核心方法繼續(xù)進(jìn)入
      runUnlockedCleanup();
    }

void runUnlockedCleanup() {
      // locked cleanup may generate notifications we can send unlocked
      if (!isHeldByCurrentThread()) {
        // 核心方法繼續(xù)進(jìn)入
        map.processPendingNotifications();
      }
    }
void processPendingNotifications() {
    RemovalNotification<K, V> notification;
    // 前面所有涉及notify的操作都會(huì)進(jìn)到相應(yīng)的queue中,然后在該主方法中進(jìn)行回調(diào)
    while ((notification = removalNotificationQueue.poll()) != null) {
      try {
        // 核心流程瓢娜,只要實(shí)現(xiàn)removalListener挂洛,通過(guò)構(gòu)造方法傳進(jìn)來(lái),然后這里就會(huì)同步調(diào)用實(shí)現(xiàn)的回調(diào)方法
        // PS第一遍看源碼一度卡在這個(gè)位置眠砾,不知道這玩意兒就一個(gè)通知機(jī)制怎么就移除元素了
        removalListener.onRemoval(notification);
      } catch (Throwable e) {
        logger.log(Level.WARNING, "Exception thrown by removal listener", e);
      }
    }
  }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末虏劲,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子褒颈,更是在濱河造成了極大的恐慌柒巫,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谷丸,死亡現(xiàn)場(chǎng)離奇詭異堡掏,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)刨疼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)泉唁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人揩慕,你說(shuō)我怎么就攤上這事亭畜。” “怎么了迎卤?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵拴鸵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我蜗搔,道長(zhǎng)劲藐,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任樟凄,我火速辦了婚禮聘芜,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘缝龄。我一直安慰自己厉膀,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布二拐。 她就那樣靜靜地躺著,像睡著了一般凳兵。 火紅的嫁衣襯著肌膚如雪百新。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,457評(píng)論 1 311
  • 那天庐扫,我揣著相機(jī)與錄音饭望,去河邊找鬼仗哨。 笑死,一個(gè)胖子當(dāng)著我的面吹牛铅辞,可吹牛的內(nèi)容都是我干的厌漂。 我是一名探鬼主播,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼斟珊,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼苇倡!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起囤踩,我...
    開(kāi)封第一講書(shū)人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤旨椒,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后堵漱,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體综慎,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年勤庐,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了示惊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡愉镰,死狀恐怖米罚,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情岛杀,我是刑警寧澤阔拳,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站类嗤,受9級(jí)特大地震影響糊肠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜遗锣,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一货裹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧精偿,春花似錦弧圆、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至叶组,卻和暖如春拯田,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背甩十。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工船庇, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留吭产,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓鸭轮,卻偏偏與公主長(zhǎng)得像臣淤,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子窃爷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360

推薦閱讀更多精彩內(nèi)容