1. 程式人生 > >Google guava cache源碼解析1--構建緩存器(3)

Google guava cache源碼解析1--構建緩存器(3)

簡單 ava iou 了解 () www. con 垃圾 緩存

此文已由作者趙計剛授權網易雲社區發布。

歡迎訪問網易雲社區,了解更多網易技術產品運營經驗。


下面介紹在LocalCache(CacheBuilder, CacheLoader)中調用的一些方法:

  • CacheBuilder-->getConcurrencyLevel()

    int getConcurrencyLevel() {
            return (concurrencyLevel == UNSET_INT) ? //是否設置了concurrencyLevel
                    DEFAULT_CONCURRENCY_LEVEL//如果沒有設置,采用默認值16
                    : concurrencyLevel;//如果設置了,采用設置的值
        }

    說明:檢查是否設置了concurrencyLevel,如果設置了,采用設置的值,如果沒有設置,采用默認值16

  • CacheBuilder-->getKeyStrength()

    //獲取鍵key的強度(默認為Strong,還有weak和soft)
        Strength getKeyStrength() {
            return MoreObjects.firstNonNull(keyStrength, Strength.STRONG);
        }

    說明:獲取key的引用類型(強度),默認為Strong(強引用類型),下表列出MoreObjects的方法firstNonNull(@Nullable T first, @Nullable T second)

    public static <T> T firstNonNull(@Nullable T first, @Nullable T second) {
        return first != null ? first : checkNotNull(second);
      }

  • CacheBuilder-->getValueStrength()

        Strength getValueStrength() {
            return MoreObjects.firstNonNull(valueStrength, Strength.STRONG);
        }

    說明:獲取value的引用類型(強度),默認為Strong(強引用類型)

  • CacheBuilder-->getExpireAfterWriteNanos()

    long getExpireAfterWriteNanos() {
            return (expireAfterWriteNanos == UNSET_INT) ? 
                    DEFAULT_EXPIRATION_NANOS
                    : expireAfterWriteNanos;
        }

    說明:獲取超時時間,如果設置了,就是設置值,如果沒設置,默認是0

  • CacheBuilder-->getInitialCapacity()

    int getInitialCapacity() {
            return (initialCapacity == UNSET_INT) ? 
                    DEFAULT_INITIAL_CAPACITY
                    : initialCapacity;
        }

    說明:獲取初始化容量,如果指定了就是用指定容量,如果沒指定,默認為16。值得註意的是,該容量是用於計算每個Segment的容量的,並不一定是每個Segment的容量,其具體使用的方法見LocalCache(CacheBuilder, CacheLoader)

  • LocalCache-->evictsBySize()

    //這裏maxWeight沒有設置值,默認為UNSET_INT,即-1
    
        boolean evictsBySize() {
            return maxWeight >= 0;
        }

    說明:這是一個與weight相關的方法,由於我們沒有設置weight,所以該方法對我們的程序沒有影響。

  • EntryFactory-->getFatory()

    /**
             * 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)//0
                        | (usesAccessQueue ? ACCESS_MASK : 0)//0
                        | (usesWriteQueue ? WRITE_MASK : 0);//WRITE_MASK-->2
                return factories[flags];//STRONG_WRITE
            }

    說明:EntryFactory是LocalCache的一個內部枚舉類,通過上述方法,獲取除了相應的EntryFactory,這裏選出的是STRONG_WRITE工廠,該工廠代碼如下:

            STRONG_WRITE {
                /**
                 * 創建新的Entry
                 */
                @Override
                <K, V> ReferenceEntry<K, V> newEntry(Segment<K, V> segment, 
                                                     K key,
                                                     int hash, 
                                                     @Nullable ReferenceEntry<K, V> next) {
                    return new StrongWriteEntry<K, V>(key, hash, next);
                }
    
                /**
                 * 將原來的Entry(original)拷貝到當下的Entry(newNext)
                 */
                @Override
                <K, V> ReferenceEntry<K, V> copyEntry(Segment<K, V> segment,
                                                      ReferenceEntry<K, V> original, 
                                                      ReferenceEntry<K, V> newNext) {
                    ReferenceEntry<K, V> newEntry = super.copyEntry(segment,
                            original, newNext);
                    copyWriteEntry(original, newEntry);
                    return newEntry;
                }
            }

    在該工廠中,指定了創建新entry的方法與復制原有entry為另一個entry的方法。

  • LocalCache-->newSegmentArray(int ssize)

    /**
         * 創建一個指定大小的Segment數組
         */
        @SuppressWarnings("unchecked")
        final Segment<K, V>[] newSegmentArray(int ssize) {
            return new Segment[ssize];
        }

    說明:該方法用於創建一個指定大小的Segment數組。關於Segment的介紹後邊會說。

  • LocalCache-->createSegment(initialCapacity,maxSegmentWeight,StatsCounter)

        Segment<K, V> createSegment(int initialCapacity, 
                                    long maxSegmentWeight,
                                    StatsCounter statsCounter) {
            return new Segment<K, V>(this, 
                                     initialCapacity, 
                                     maxSegmentWeight,
                                     statsCounter);
        }

    該方法用於為之前創建的Segment數組的每一個元素賦值。

    下邊列出Segment類的一些屬性和方法:

    final LocalCache<K, V> map;// 外部類的一個實例
    
            /** 該Segment中已經存在緩存的個數  */
            volatile int count;
    
            /**
             * 指定是下邊的AtomicReferenceArray<ReferenceEntry<K, V>> table,即擴容也是只擴自己的Segment
             * The table is expanded when its size exceeds this threshold. (The
             * value of this field is always {@code (int) (capacity * 0.75)}.)
             */
            int threshold;
    
            /**
             * 每個Segment中的table
             */
            volatile AtomicReferenceArray<ReferenceEntry<K, V>> table;
    
            /**
             * The maximum weight of this segment. UNSET_INT if there is no maximum.
             */
            final long maxSegmentWeight;
    
            /**
             * map中當前元素的一個隊列,隊列元素根據write time進行排序,每write一個元素就將該元素加在隊列尾部
             */
            @GuardedBy("this")
            final Queue<ReferenceEntry<K, V>> 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<ReferenceEntry<K, V>> accessQueue;
    
            Segment(LocalCache<K, V> map, int initialCapacity,
                    long maxSegmentWeight, StatsCounter statsCounter) {
                this.map = map;
                this.maxSegmentWeight = maxSegmentWeight;//0
                this.statsCounter = checkNotNull(statsCounter);
                initTable(newEntryArray(initialCapacity));
    
                writeQueue = map.usesWriteQueue() ? //過期時間>0
                             new WriteQueue<K, V>() //WriteQueue
                             : LocalCache.<ReferenceEntry<K, V>> discardingQueue();
    
                accessQueue = map.usesAccessQueue() ? //false
                              new AccessQueue<K, V>()
                              : LocalCache.<ReferenceEntry<K, V>> discardingQueue();
            }
    
            AtomicReferenceArray<ReferenceEntry<K, V>> newEntryArray(int size) {
                return new AtomicReferenceArray<ReferenceEntry<K, V>>(size);//new Object[size];
            }
    
            void initTable(AtomicReferenceArray<ReferenceEntry<K, V>> newTable) {
                this.threshold = newTable.length() * 3 / 4; // 0.75
                if (!map.customWeigher() && this.threshold == maxSegmentWeight) {
                    // prevent spurious expansion before eviction
                    this.threshold++;
                }
                this.table = newTable;
            }

    Segment的構造器完成了三件事兒:為變量復制 + 初始化Segment的table + 構建相關隊列

    • initTable(newEntryArray(initialCapacity))源代碼在Segment類中已給出:初始化table的步驟簡述為:創建一個指定個數的ReferenceEntry數組,計算擴容值。

    • 其他隊列不說了,這裏實際上只用到了WriteQueue,建立該Queue的目的是用於實現LRU緩存回收算法

到目前為止,guava cache的完整的一個數據結構基本上就建立起來了。最後再總結一下。

guava cache的數據結構:


技術分享圖片

guava cache的數據結構的構建流程:

1)構建CacheBuilder實例cacheBuilder

2)cacheBuilder實例指定緩存器LocalCache的初始化參數

3)cacheBuilder實例使用build()方法創建LocalCache實例(簡單說成這樣,實際上復雜一些)

3.1)首先為各個類變量賦值(通過第二步中cacheBuilder指定的初始化參數以及原本就定義好的一堆常量)

3.2)之後創建Segment數組

3.3)最後初始化每一個Segment[i]

3.3.1)為Segment屬性賦值

3.3.2)初始化Segment中的table,即一個ReferenceEntry數組(每一個key-value就是一個ReferenceEntry)

3.3.3)根據之前類變量的賦值情況,創建相應隊列,用於LRU緩存回收算法

這裏,我們就用開頭給出的代碼實例,來看一下,最後構建出來的cache結構是個啥:

顯示指定:

expireAfterWriteNanos==20min maximumSize==1000

默認值:

concurrency_level==4(用於計算Segment個數) initial_capcity==16 (用於計算每個Segment容量)

keyStrength==STRONG   valueStrength==STRONG

計算出:

entryFactory==STRONG_WRITE

segmentCount==4:Segment個數,一個剛剛大於等於concurrency_level且是2的幾次方的一個數

segmentCapacity==initial_capcity/segmentCount==4:用來計算每個Segment能放置的entry個數的一個值,一個剛剛等於initial_capcity/segmentCount或者比initial_capcity/segmentCount大1的數(關鍵看是否除盡)

segmentSize==4:每個Segment能放置的entry個數,剛剛>=segmentCapacity&&是2的幾次方的數

segments==Segment[segmentCount]==Segment[4]

segments[i]:

  • 包含一個ReferenceEntry[segmentSize]==ReferenceEntry[4]

  • WriteQueue:用於LRU算法的隊列

  • threshold==newTable.length()*3/4==segmentSize*3/4==3:每個Segment中有了3個Entry(key-value),就會擴容,擴容機制以後在添加Entry的時候再講


免費領取驗證碼、內容安全、短信發送、直播點播體驗包及雲服務器等套餐

更多網易技術、產品、運營經驗分享請點擊。


相關文章:
【推薦】 JVM內存回收區域+對象存活的判斷+引用類型+垃圾回收線程
【推薦】 JVM鎖實現探究2:synchronized深探

Google guava cache源碼解析1--構建緩存器(3)