Guava Cache系列之三:源碼分析

概述

前面的兩篇文章(Guava Cache系列之一:如何加載緩存Guava Cache系列之二:如何回收緩存)介紹了Guava Cache的使用袁梗,下面從源碼來(lái)看一下Guava Cache的設(shè)計(jì)和實(shí)現(xiàn)

源碼分析

構(gòu)造函數(shù)

LocalCache(
      CacheBuilder<? super K, ? super V> builder, @Nullable CacheLoader<? super K, V> loader) {
    //并發(fā)程度,根據(jù)我們傳的參數(shù)和默認(rèn)最大值中選取小者噪珊。
    //如果沒有指定該參數(shù)的情況下袁波,CacheBuilder將其置為UNSET_INT即為-1
    //getConcurrencyLevel方法獲取時(shí),如果為-1就返回默認(rèn)值4
    //否則返回用戶傳入的參數(shù)
    concurrencyLevel = Math.min(builder.getConcurrencyLevel(), MAX_SEGMENTS);
    //鍵值的引用類型蚁署,沒有指定的話,默認(rèn)為強(qiáng)引用類型
    keyStrength = builder.getKeyStrength();
    valueStrength = builder.getValueStrength();
    //判斷相同的方法搀暑,強(qiáng)引用類型就是Equivalence.equals()
    keyEquivalence = builder.getKeyEquivalence();
    valueEquivalence = builder.getValueEquivalence();

    maxWeight = builder.getMaximumWeight();
    weigher = builder.getWeigher();
    expireAfterAccessNanos = builder.getExpireAfterAccessNanos();
    expireAfterWriteNanos = builder.getExpireAfterWriteNanos();
    refreshNanos = builder.getRefreshNanos();
    //移除消息監(jiān)聽器
    removalListener = builder.getRemovalListener();
    //如果我們指定了移除消息監(jiān)聽器的話沥阳,會(huì)創(chuàng)建一個(gè)隊(duì)列,臨時(shí)保存移除的內(nèi)容
    removalNotificationQueue = (removalListener == NullListener.INSTANCE)
        ? LocalCache.<RemovalNotification<K, V>>discardingQueue()
        : new ConcurrentLinkedQueue<RemovalNotification<K, V>>();

    ticker = builder.getTicker(recordsTime());
    //創(chuàng)建新的緩存內(nèi)容(entry)的工廠自点,會(huì)根據(jù)引用類型選擇對(duì)應(yīng)的工廠
    entryFactory = EntryFactory.getFactory(keyStrength, usesAccessEntries(), usesWriteEntries());
    globalStatsCounter = builder.getStatsCounterSupplier().get();
    defaultLoader = loader;
    //初始化緩存容量桐罕,默認(rèn)為16
    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;
    //根據(jù)并發(fā)程度來(lái)計(jì)算segement數(shù)組的大小(大于等于concurrencyLevel的最小的2的冪桂敛,這里即為4)
    while (segmentCount < concurrencyLevel
           && (!evictsBySize() || segmentCount * 20 <= maxWeight)) {
      ++segmentShift;
      segmentCount <<= 1;
    }
    //這里的segmentShift和segmentMask用來(lái)打散entry功炮,讓緩存內(nèi)容盡量均勻分布在每個(gè)segment下
    this.segmentShift = 32 - segmentShift;
    segmentMask = segmentCount - 1;
    //這里進(jìn)行初始化segment數(shù)組,大小即為4
    this.segments = newSegmentArray(segmentCount);
    //每個(gè)segment的容量术唬,總?cè)萘?segment的大小薪伏,向上取整,這里就是16/4=4
    int segmentCapacity = initialCapacity / segmentCount;
    if (segmentCapacity * segmentCount < initialCapacity) {
      ++segmentCapacity;
    }
    //這里計(jì)算每個(gè)Segment[i]下的table的大小
    int segmentSize = 1;
    //SegmentSize為小于segmentCapacity的最大的2的冪粗仓,這里為4
    while (segmentSize < segmentCapacity) {
      segmentSize <<= 1;
    }
    //初始化每個(gè)segment[i]
    //注:根據(jù)權(quán)重的方法使用較少嫁怀,這里走else分支
    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());
      }
    }
  }

成員變量簡(jiǎn)介如下:

  • static final int MAXIMUM_CAPACITY = 1 << 30:緩存最大容量,該數(shù)值必須是2的冪借浊,同時(shí)小于這個(gè)最大值2^30

  • static final int MAX_SEGMENTS = 1 << 16:Segment數(shù)組最大容量

  • static final int CONTAINS_VALUE_RETRIES = 3:containsValue方法的重試次數(shù)

  • static final int DRAIN_THRESHOLD = 0x3F(63):Number of cache access operations that can be buffered per segment before the cache's recency ordering information is updated. This is used to avoid lock contention by recording a memento of reads and delaying a lock acquisition until the threshold is crossed or a mutation occurs.

  • static final int DRAIN_MAX = 16:一次清理操作中塘淑,最大移除的entry數(shù)量

  • final int segmentMask:定位segment

  • final int segmentShift:定位segment,同時(shí)讓entry分布均勻蚂斤,盡量平均分布在每個(gè)segment[i]中

  • final Segment<K, V>[] segments:segment數(shù)組存捺,每個(gè)元素下都是一個(gè)HashTable

  • final int concurrencyLevel:并發(fā)程度,用來(lái)計(jì)算segment數(shù)組的大小曙蒸。segment數(shù)組的大小正決定了并發(fā)的程度

  • final Equivalence<Object> keyEquivalence:key比較方式

  • final Equivalence<Object> valueEquivalence:value比較方式

  • final Strength keyStrength:key引用類型

  • final Strength valueStrength:value引用類型

  • final long maxWeight:最大權(quán)重

  • final Weigher<K, V> weigher:計(jì)算每個(gè)entry權(quán)重的接口

  • final long expireAfterAccessNanos:一個(gè)entry訪問后多久過期

  • final long expireAfterWriteNanos:一個(gè)entry寫入后多久過期

  • final long refreshNanos:一個(gè)entry寫入多久后進(jìn)行刷新

  • final Queue<RemovalNotification<K, V>> removalNotificationQueue:移除監(jiān)聽器使用隊(duì)列

  • final RemovalListener<K, V> removalListener:entry過期移除或者gc回收(弱引用和軟引用)將會(huì)通知的監(jiān)聽器

  • final Ticker ticker:統(tǒng)計(jì)時(shí)間

  • final EntryFactory entryFactory:創(chuàng)建entry的工廠

  • final StatsCounter globalStatsCounter:全局緩存性能統(tǒng)計(jì)器(命中捌治、未命中、put成功逸爵、失敗次數(shù)等)

  • final CacheLoader<? super K, V> defaultLoader:默認(rèn)的緩存加載器

通過源碼可以看出具滴,Guava Cache的初始化和使用到的數(shù)據(jù)結(jié)構(gòu)和ConcurrentHashMap是很相似的。具體可以參考ConcurrentHashMap

get

核心代碼邏輯在com.google.common.cache.LocalCache.Segment#get(K, int, com.google.common.cache.CacheLoader<? super K,V>)

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

核心代碼在com.google.common.cache.LocalCache.Segment#lockedGetOrLoad

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;
        //當(dāng)前segment下的HashTable
        AtomicReferenceArray<ReferenceEntry<K, V>> table = this.table;
        //這里也是為什么table的大小要為2的冪(最后index范圍剛好在0-table.length()-1)
        int index = hash & (table.length() - 1);
        ReferenceEntry<K, V> first = table.get(index);
        //在鏈表上查找
        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();
            //如果正在載入中师倔,就不需要?jiǎng)?chuàng)建构韵,只需要等待載入完成讀取即可
            if (valueReference.isLoading()) {
              createNewEntry = false;
            } else {
              V value = valueReference.get();
              // 被gc回收(在弱引用和軟引用的情況下會(huì)發(fā)生)
              if (value == null) {
                enqueueNotification(entryKey, hash, valueReference, RemovalCause.COLLECTED);
              } else if (map.isExpired(e, now)) {
                // 過期
                enqueueNotification(entryKey, hash, valueReference, RemovalCause.EXPIRED);
              } else {
                //存在并且沒有過期,更新訪問隊(duì)列并記錄命中信息趋艘,返回value
                recordLockedRead(e, now);
                statsCounter.recordHits(1);
                // we were concurrent with loading; don't consider refresh
                return value;
              }

              // 對(duì)于被gc回收和過期的情況疲恢,從寫隊(duì)列和訪問隊(duì)列中移除
              // 因?yàn)樵诤竺嬷匦螺d入后,會(huì)再次添加到隊(duì)列中
              writeQueue.remove(e);
              accessQueue.remove(e);
              this.count = newCount; // write-volatile
            }
            break;
          }
        }

        if (createNewEntry) {
          //先創(chuàng)建一個(gè)loadingValueReference瓷胧,表示正在載入
          loadingValueReference = new LoadingValueReference<K, V>();

          if (e == null) {
            //如果當(dāng)前鏈表為空显拳,先創(chuàng)建一個(gè)頭結(jié)點(diǎn)
            e = newEntry(key, hash, first);
            e.setValueReference(loadingValueReference);
            table.set(index, e);
          } else {
            e.setValueReference(loadingValueReference);
          }
        }
      } finally {
        //解鎖
        unlock();
        //執(zhí)行清理
        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.
          synchronized (e) {
            //異步加載
            return loadSync(key, hash, loadingValueReference, loader);
          }
        } finally {
          //記錄未命中
          statsCounter.recordMisses(1);
        }
      } else {
        // 等待加載進(jìn)來(lái)然后讀取即可
        return waitForLoadingValue(e, key, valueReference);
      }
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市搓萧,隨后出現(xiàn)的幾起案子杂数,更是在濱河造成了極大的恐慌宛畦,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件揍移,死亡現(xiàn)場(chǎng)離奇詭異次和,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)那伐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門踏施,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人罕邀,你說(shuō)我怎么就攤上這事畅形。” “怎么了诉探?”我有些...
    開封第一講書人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵日熬,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我肾胯,道長(zhǎng)碍遍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任阳液,我火速辦了婚禮怕敬,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘帘皿。我一直安慰自己东跪,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開白布鹰溜。 她就那樣靜靜地躺著虽填,像睡著了一般。 火紅的嫁衣襯著肌膚如雪曹动。 梳的紋絲不亂的頭發(fā)上斋日,一...
    開封第一講書人閱讀 49,772評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音墓陈,去河邊找鬼恶守。 笑死,一個(gè)胖子當(dāng)著我的面吹牛贡必,可吹牛的內(nèi)容都是我干的兔港。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼仔拟,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼衫樊!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤科侈,失蹤者是張志新(化名)和其女友劉穎载佳,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體臀栈,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡刚盈,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了挂脑。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡欲侮,死狀恐怖崭闲,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情威蕉,我是刑警寧澤刁俭,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站韧涨,受9級(jí)特大地震影響牍戚,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜虑粥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一如孝、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧娩贷,春花似錦奢驯、人聲如沸莽囤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)廉涕。三九已至,卻和暖如春孕暇,著一層夾襖步出監(jiān)牢的瞬間蜓氨,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工突倍, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留腔稀,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓羽历,卻偏偏與公主長(zhǎng)得像烧颖,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子窄陡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348

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

  • 一跳夭、基本數(shù)據(jù)類型 注釋 單行注釋:// 區(qū)域注釋:/* */ 文檔注釋:/** */ 數(shù)值 對(duì)于byte類型而言...
    龍貓小爺閱讀 4,257評(píng)論 0 16
  • Guava Cache以下的特性: automatic loading of entries into the c...
    小鋤禾閱讀 8,615評(píng)論 2 11
  • java筆記第一天 == 和 equals ==比較的比較的是兩個(gè)變量的值是否相等涂圆,對(duì)于引用型變量表示的是兩個(gè)變量...
    jmychou閱讀 1,488評(píng)論 0 3
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法们镜,類相關(guān)的語(yǔ)法,內(nèi)部類的語(yǔ)法润歉,繼承相關(guān)的語(yǔ)法模狭,異常的語(yǔ)法,線程的語(yǔ)...
    子非魚_t_閱讀 31,598評(píng)論 18 399
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理踩衩,服務(wù)發(fā)現(xiàn)嚼鹉,斷路器,智...
    卡卡羅2017閱讀 134,629評(píng)論 18 139