第二章 Google guava cache源碼解析1--構建緩存器


1、guava cache

  • 當下最常用最簡單的本地緩存
  • 線程安全本地緩存
  • 類似於ConcurrentHashMap(或者說成就是一個ConcurrentHashMap,只是在其上多添加了一些功能)

2、使用實例

具體在實際中使用的例子,去查看《第七章 企業項目開發--本地緩存guava cache》,下面只列出測試實例:

import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;

public class Hello{
    
    LoadingCache<String, String> testCache = CacheBuilder.newBuilder()
            .expireAfterWrite(20, TimeUnit.MINUTES)// 緩存20分鍾
            .maximumSize(1000)// 最多緩存1000個對象
            .build(new CacheLoader<String, String>() {
                public String load(String key) throws Exception {
                    if(key.equals("hi")){
                        return null;
                    }
                    return key+"-world";
                }
            });
    
    public static void main(String[] args){
        Hello hello = new Hello();
        System.out.println(hello.testCache.getIfPresent("hello"));//null
        hello.testCache.put("123", "nana");//存放緩存
        System.out.println(hello.testCache.getIfPresent("123"));//nana
        try {
            System.out.println(hello.testCache.get("hello"));//hello-world
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println(hello.testCache.getIfPresent("hello"));//hello-world
        /***********測試null*************/
        System.out.println(hello.testCache.getIfPresent("hi"));//null
        try {
            System.out.println(hello.testCache.get("hi"));//拋異常
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        
    }
}
View Code

在這個方法中,基本已經覆蓋了guava cache常用的部分。

  • 構造緩存器
    • 緩存器的構建沒有使用構造器而不是使用了構建器模式,這是在存在多個可選參數的時候,最合適的一種配置參數的方式,具體參看《effective Java(第二版)》第二條建議。
  • 常用的三個方法
    • get(Object key)
    • getIfPresent(Object key)
    • put(Object key, Object value)

3、源代碼

在閱讀源代碼之前,強烈建議,先看一下"Java並發包類源碼解析"中的《第二章 ConcurrentHashMap源碼解析》,鏈接如下:

http://www.cnblogs.com/java-zhao/p/5113317.html

對於源碼部分,由於整個代碼的核心類LocalCache有5000多行,所以只介紹上邊給出的實例部分的相關源碼解析。本節只說一下緩存器的構建,即如下代碼部分:

    LoadingCache<String, String> testCache = CacheBuilder.newBuilder()
            .expireAfterWrite(20, TimeUnit.MINUTES)// 緩存20分鍾(時間起點:entry的創建或替換(即修改))
            //.expireAfterAccess(10, TimeUnit.MINUTES)//緩存10分鍾(時間起點:entry的創建或替換(即修改)或最后一次訪問)
            .maximumSize(1000)// 最多緩存1000個對象
            .build(new CacheLoader<String, String>() {
                public String load(String key) throws Exception {
                    if(key.equals("hi")){
                        return null;
                    }
                    return key+"-world";
                }
            });
View Code

說明:該代碼的load()方法會在之后將get(Object key)的時候再說,這里先不說了。

對於這一塊兒,由於guava cache這一塊兒的代碼雖然不難,但是容易看的跑偏,一會兒就不知道跑到哪里去了,所以我下邊先給出guava cache的數據結構以及上述代碼的執行流程,然后大家帶着這個數據結構和執行流程去分析下邊的源代碼,分析完源代碼之后,我在最后還會再將cache的數據結構和構建緩存器的執行流程給出,並會結合我們給出的開頭實例代碼來套一下整個流程,最后畫出初始化構建出來的緩存器(其實,這個緩存器就是上邊以及文末給出的cache的數據結構圖)。

 

guava cache的數據結構圖:

需要說明的是:

  • 每一個Segment中的有效隊列(廢棄隊列不算)的個數最多可能不止一個
  • 上圖與ConcurrentHashMap及其類似,其中的ReferenceEntry[i]用於存放key-value
  • 每一個ReferenceEntry[i]都會存放一個鏈表,當然采用的也是Entry替換的方式。
  • 隊列用於實現LRU緩存回收算法
  • 多個Segment之間互不打擾,可以並發執行
  • 各個Segment的擴容只需要擴自己的就好,與其他Segment無關
  • 根據需要設置好初始化容量與並發水平參數,可以有效避免擴容帶來的昂貴代價,但是設置的太大了,又會耗費很多內存,要衡量好

后邊三條與ConcurrentHashMap一樣

 

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緩存回收算法

 

類結構:(這個不看也罷)

 

  • CacheBuilder:設置LocalCache的相關參數,並創建LocalCache實例
  • CacheLoader:有用的部分就是一個load(),用於實現"取緩存-->若不存在,先計算,在緩存-->取緩存"的原子操作
  • LocalCache:整個guava cache的核心類,包含了guava cache的數據結構以及基本的緩存的操作方法
  • LocalLoadingCache:LocalCache的一個靜態內部類,這里的get(K key)是外部調用get(K key)入口
  • LoadingCache接口:繼承於Cache接口,定義了get(K key)
  • Cache接口:定義了getIfPresent(Object key)和put(K key, V value)
  • LocalManualCache:LocalCache的一個靜態內部類,是LocalLoadingCache的父類,這里的getIfPresent(Object key)和put(K key, V value)也是外部方法的入口

關於上邊的這些說明,結合之后的源碼進行看就好了。

注:如果在源碼中有一些注釋與最后的套例子的注釋不同的話,以后者為准

3.1、構建CacheBuilder+為LocalCache設置相關參數+創建LocalCache實例

CacheBuilder的一些屬性:

    private static final int DEFAULT_INITIAL_CAPACITY = 16;//用於計算每個Segment中的hashtable的大小
    private static final int DEFAULT_CONCURRENCY_LEVEL = 4;//用於計算有幾個Segment
    private static final int DEFAULT_EXPIRATION_NANOS = 0;//默認的緩存過期時間
    
    static final int UNSET_INT = -1;
    
    int initialCapacity = UNSET_INT;//用於計算每個Segment中的hashtable的大小
    int concurrencyLevel = UNSET_INT;//用於計算有幾個Segment
    long maximumSize = UNSET_INT;//cache中最多能存放的緩存entry個數
    long maximumWeight = UNSET_INT;
    
    Strength keyStrength;//鍵的引用類型(strong、weak、soft)
    Strength valueStrength;//值的引用類型(strong、weak、soft)

    long expireAfterWriteNanos = UNSET_INT;//緩存超時時間(起點:緩存被創建或被修改)
    long expireAfterAccessNanos = UNSET_INT;//緩存超時時間(起點:緩存被創建或被修改或被訪問)
View Code

CacheBuilder-->newCacheBuilder():創建一個CacheBuilder實例

    /**
     * 采用默認的設置(如下)創造一個新的CacheBuilder實例
     * 1、strong keys
     * 2、strong values
     * 3、no automatic eviction of any kind.
     */
    public static CacheBuilder<Object, Object> newBuilder() {
        return new CacheBuilder<Object, Object>();//new 一個實例
    }
View Code

接下來,使用構建器模式指定一些屬性值(這里的話,就是超時時間:expireAfterWriteNanos+cache中最多能放置的entry個數:maximumSize),這里的entry指的就是一個緩存(key-value對)

CacheBuilder-->expireAfterWrite(long duration, TimeUnit unit)

    /**
     * 指明每一個entry(key-value)在緩存中的過期時間
     * 1、時間的參考起點:entry的創建或值的修改
     * 2、過期的entry也許會被計入緩存個數size(也就是說緩存個數不僅僅只有存活的entry)
     * 3、但是過期的entry永遠不會被讀寫
     */
    public CacheBuilder<K, V> expireAfterWrite(long duration, TimeUnit unit) {
        /*
         * 檢查之前是否已經設置過緩存超時時間
         */
        checkState(expireAfterWriteNanos == UNSET_INT,//正確條件:之前沒有設置過緩存超時時間
                   "expireAfterWrite was already set to %s ns",//不符合正確條件的錯誤信息
                   expireAfterWriteNanos);
        /*
         * 檢查設置的超時時間是否大於等於0,當然,通常情況下,我們不會設置緩存為0
         */
        checkArgument(duration >= 0, //正確條件
                      "duration cannot be negative: %s %s",//不符合正確條件的錯誤信息,下邊的是錯誤信息中的錯誤參數
                      duration, 
                      unit);
        this.expireAfterWriteNanos = unit.toNanos(duration);//根據輸入的時間值與時間單位,將時間值轉換為納秒
        return this;
    }
View Code

注意:

  • 設置超時時間,注意時間的起點是entry的創建或替換(修改)
  • expireAfterAccess(long duration, TimeUnit unit)方法的時間起點:entry的創建或替換(修改)或被訪問

CacheBuilder-->maximumSize(long size)

    /**
     * 指定cache中最多能存放的entry(key-value)個數maximumSize
     * 注意:
     * 1、在entry個數還未達到這個指定個數maximumSize的時候,可能就會發生緩存回收
     * 上邊這種情況發生在cache size接近指定個數maximumSize,
     * cache就會回收那些很少會再被用到的緩存(這些緩存會使最近沒有被用到或很少用到的),其實說白了就是LRU算法回收緩存
     * 2、maximumSize與maximumWeight不能一起使用,其實后者也很少會使用
     */
    public CacheBuilder<K, V> maximumSize(long size) {
        /* 檢查maximumSize是否已經被設置過了 */
        checkState(this.maximumSize == UNSET_INT,
                   "maximum size was already set to %s", 
                   this.maximumSize);
        /* 檢查maximumWeight是否已經被設置過了(這就是上邊說的第二條)*/
        checkState(this.maximumWeight == UNSET_INT,
                   "maximum weight was already set to %s", 
                   this.maximumWeight);
        /* 這是與maximumWeight配合的一個屬性 */
        checkState(this.weigher == null,
                   "maximum size can not be combined with weigher");
        /* 檢查設置的maximumSize是不是>=0,通常不會設置為0,否則不會起到緩存作用 */
        checkArgument(size >= 0, "maximum size must not be negative");
        this.maximumSize = size;
        return this;
    }
View Code

注意:

  • 設置整個cache(而非每個Segment)中最多可存放的entry的個數

CacheBuilder-->build(CacheLoader<? super K1, V1> loader)

    /**
     * 建立一個cache,該緩存器通過使用傳入的CacheLoader,
     * 既可以獲取已給定key的value,也能夠自動的計算和獲取緩存(這說的就是get(Object key)的三步原子操作)
     * 當然,這里是線程安全的,線程安全的運行方式與ConcurrentHashMap一致
     */
    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);
    }
View Code

注意:

  • 要看懂該方法,需要了解一些泛型方法的使用方式與泛型限界
  • 該方法的返回值是一個LoadingCache接口的實現類LocalLoadingCache實例
  • 在build方法需要傳入一個CacheLoader的實例,實際使用中使用了匿名內部類來實現的,源碼的話,就是一個無參構造器,什么也沒做,傳入CacheLoader實例的意義就是"類結構"部分所說的load()方法

 在上邊調用build時,整個代碼的執行權其實就交給了LocalCache.

 

3.2、LocalCache

LocalLoadingCahe構造器

    static class LocalLoadingCache<K, V> extends LocalManualCache<K, V>
                                         implements LoadingCache<K, V> {

        LocalLoadingCache(CacheBuilder<? super K, ? super V> builder,
                          CacheLoader<? super K, V> loader) {
            super(new LocalCache<K, V>(builder, checkNotNull(loader)));
        }
View Code

說明:在該內部類的無參構造器的調用中,

1)首先要保證傳入的CacheLoader實例非空,

2)其次創建了一個LocalCache的實例出來,

3)最后調用父類LocalManualCache的私有構造器將第二步創建出來的LocalCache實例賦給LocalCache的類變量,完成初始化。

這里最重要的就是第二步,下面着重講第二步:

LocalCache的一些屬性

    /** 最大容量(2的30次方),即最多可存放2的30次方個entry(key-value) */
    static final int MAXIMUM_CAPACITY = 1 << 30;

    /** 最多多少個Segment(2的16次方)*/
    static final int MAX_SEGMENTS = 1 << 16;

    /** 用於選擇Segment */
    final int segmentMask;

    /** 用於選擇Segment,盡量將hash打散 */
    final int segmentShift;

    /** 底層數據結構,就是一個Segment數組,而每一個Segment就是一個hashtable */
    final Segment<K, V>[] segments;

    /** 
     * 並發水平,這是一個用於計算Segment個數的一個數,
     * Segment個數是一個剛剛大於或等於concurrencyLevel的數
     */
    final int concurrencyLevel;

    /** 鍵的引用類型(strong、weak、soft) */
    final Strength keyStrength;

    /** 值的引用類型(strong、weak、soft) */
    final Strength valueStrength;

    /** The maximum weight of this map. UNSET_INT if there is no maximum. 
     * 如果沒有設置,就是-1
     */
    final long maxWeight;

    final long expireAfterAccessNanos;

    final long expireAfterWriteNanos;

    /** Factory used to create new entries. */
    final EntryFactory entryFactory;

    /** 默認的緩存加載器,用於做一些緩存加載操作(其實就是load),實現三步原子操作*/
    @Nullable
    final CacheLoader<? super K, V> defaultLoader;

    /** 默認的緩存加載器,用於做一些緩存加載操作(其實就是load),實現三步原子操作*/
    @Nullable
    final CacheLoader<? super K, V> defaultLoader;
View Code

說明:關於這些屬性的含義,看注釋+CacheBuilder部分的屬性注釋+ConcurrentHashMap的屬性注釋

 

LocalCache-->LocalCache(CacheBuilder, CacheLoader)

    /**
     * 創建一個新的、空的map(並且指定策略、初始化容量和並發水平)
     */
    LocalCache(CacheBuilder<? super K, ? super V> builder,
               @Nullable CacheLoader<? super K, V> loader) {
        /*
         * 默認並發水平是4,即四個Segment(但要注意concurrencyLevel不一定等於Segment個數)
         * Segment個數:一個剛剛大於或等於concurrencyLevel且是2的幾次方的一個數
         */
        concurrencyLevel = Math
                .min(builder.getConcurrencyLevel(), MAX_SEGMENTS);

        keyStrength = builder.getKeyStrength();//默認為Strong,即強引用
        valueStrength = builder.getValueStrength();//默認為Strong,即強引用

        // 緩存超時(時間起點:entry的創建或替換(即修改))
        expireAfterWriteNanos = builder.getExpireAfterWriteNanos();
        // 緩存超時(時間起點:entry的創建或替換(即修改)或最后一次訪問)
        expireAfterAccessNanos = builder.getExpireAfterAccessNanos();
        //創建entry的工廠
        entryFactory = EntryFactory.getFactory(keyStrength,
                                                  usesAccessEntries(), 
                                                  usesWriteEntries());
        //默認的緩存加載器
        defaultLoader = loader;

        // 初始化容量為16,整個cache可以放16個緩存entry
        int initialCapacity = Math.min(builder.getInitialCapacity(),
                                       MAXIMUM_CAPACITY);

        int segmentShift = 0;
        int segmentCount = 1;
        //循環條件的&&后邊的內容是關於weight的,由於沒有設置maxWeight,所以其值為-1-->evictsBySize()返回false
        while (segmentCount < concurrencyLevel
                && (!evictsBySize() || segmentCount * 20 <= maxWeight)) {
            ++segmentShift;
            segmentCount <<= 1;//找一個剛剛大於或等於concurrencyLevel的Segment數
        }
        this.segmentShift = 32 - segmentShift;
        segmentMask = segmentCount - 1;

        this.segments = newSegmentArray(segmentCount);//創建指定大小的數組

        int segmentCapacity = initialCapacity / segmentCount;//計算每一個Segment中的容量的值,剛剛大於等於initialCapacity/segmentCount
        if (segmentCapacity * segmentCount < initialCapacity) {
            ++segmentCapacity;
        }

        int segmentSize = 1;//每一個Segment的容量
        while (segmentSize < segmentCapacity) {
            segmentSize <<= 1;//剛剛>=segmentCapacity&&是2的幾次方的數
        }

        if (evictsBySize()) {//由於沒有設置maxWeight,所以其值為-1-->evictsBySize()返回false
            // 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());
            }
        }
    }
View Code

說明:這里的代碼就是整個LocalCache實例的創建過程,非常重要!!!

 

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

  • CacheBuilder-->getConcurrencyLevel()
    int getConcurrencyLevel() {
            return (concurrencyLevel == UNSET_INT) ? //是否設置了concurrencyLevel
                    DEFAULT_CONCURRENCY_LEVEL//如果沒有設置,采用默認值16
                    : concurrencyLevel;//如果設置了,采用設置的值
        }
    View Code
    說明:檢查是否設置了concurrencyLevel,如果設置了,采用設置的值,如果沒有設置,采用默認值16

 

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

    說明:獲取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);
      }
    View Code

 

  • CacheBuilder-->getValueStrength()
        Strength getValueStrength() {
            return MoreObjects.firstNonNull(valueStrength, Strength.STRONG);
        }
    View Code

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

 

  • CacheBuilder-->getExpireAfterWriteNanos()
    long getExpireAfterWriteNanos() {
            return (expireAfterWriteNanos == UNSET_INT) ? 
                    DEFAULT_EXPIRATION_NANOS
                    : expireAfterWriteNanos;
        }
    View Code

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

 

  • CacheBuilder-->getInitialCapacity()
    int getInitialCapacity() {
            return (initialCapacity == UNSET_INT) ? 
                    DEFAULT_INITIAL_CAPACITY
                    : initialCapacity;
        }
    View Code

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

 

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

    說明:這是一個與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
            }
    View Code

    說明: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;
                }
            }
    View Code

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

     

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

    說明:該方法用於創建一個指定大小的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);
        }
    View Code

    該方法用於為之前創建的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;
            }
    View Code

    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的時候再講


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM