一、guava cachehtml
二、使用實例java
具體在實際中使用的例子,去查看《第七章 企業項目開發--本地緩存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(); } } }
在這個方法中,基本已經覆蓋了guava cache經常使用的部分。數組
三、源代碼緩存
在閱讀源代碼以前,強烈建議,先看一下"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"; } });
說明:該代碼的load()方法會在以後將get(Object key)的時候再說,這裏先不說了。ide
對於這一起,因爲guava cache這一起的代碼雖然不難,可是容易看的跑偏,一下子就不知道跑到哪裏去了,因此我下邊先給出guava cache的數據結構以及上述代碼的執行流程,而後你們帶着這個數據結構和執行流程去分析下邊的源代碼,分析完源代碼以後,我在最後還會再將cache的數據結構和構建緩存器的執行流程給出,並會結合咱們給出的開頭實例代碼來套一下整個流程,最後畫出初始化構建出來的緩存器(其實,這個緩存器就是上邊以及文末給出的cache的數據結構圖)。post
guava cache的數據結構圖:
須要說明的是:
後邊三條與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緩存回收算法
類結構:(這個不看也罷)
關於上邊的這些說明,結合以後的源碼進行看就行了。
注:若是在源碼中有一些註釋與最後的套例子的註釋不一樣的話,之後者爲準
3.一、構建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;//緩存超時時間(起點:緩存被建立或被修改或被訪問)
CacheBuilder-->newCacheBuilder():建立一個CacheBuilder實例
/** * 採用默認的設置(以下)創造一個新的CacheBuilder實例 * 一、strong keys * 二、strong values * 三、no automatic eviction of any kind. */ public static CacheBuilder<Object, Object> newBuilder() { return new CacheBuilder<Object, Object>();//new 一個實例 }
接下來,使用構建器模式指定一些屬性值(這裏的話,就是超時時間:expireAfterWriteNanos+cache中最多能放置的entry個數:maximumSize),這裏的entry指的就是一個緩存(key-value對)
CacheBuilder-->expireAfterWrite(long duration, TimeUnit unit)
/** * 指明每個entry(key-value)在緩存中的過時時間 * 一、時間的參考起點:entry的建立或值的修改 * 二、過時的entry也許會被計入緩存個數size(也就是說緩存個數不只僅只有存活的entry) * 三、可是過時的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; }
注意:
CacheBuilder-->maximumSize(long size)
/** * 指定cache中最多能存放的entry(key-value)個數maximumSize * 注意: * 一、在entry個數還未達到這個指定個數maximumSize的時候,可能就會發生緩存回收 * 上邊這種狀況發生在cache size接近指定個數maximumSize, * cache就會回收那些不多會再被用到的緩存(這些緩存會使最近沒有被用到或不多用到的),其實說白了就是LRU算法回收緩存 * 二、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; }
注意:
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); }
注意:
在上邊調用build時,整個代碼的執行權其實就交給了LocalCache.
3.二、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))); }
說明:在該內部類的無參構造器的調用中,
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;
說明:關於這些屬性的含義,看註釋+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()); } } }
說明:這裏的代碼就是整個LocalCache實例的建立過程,很是重要!!!
下面介紹在LocalCache(CacheBuilder, CacheLoader)中調用的一些方法:
int getConcurrencyLevel() { return (concurrencyLevel == UNSET_INT) ? //是否設置了concurrencyLevel DEFAULT_CONCURRENCY_LEVEL//若是沒有設置,採用默認值16 : concurrencyLevel;//若是設置了,採用設置的值 }
//獲取鍵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); }
Strength getValueStrength() { return MoreObjects.firstNonNull(valueStrength, Strength.STRONG); }
說明:獲取value的引用類型(強度),默認爲Strong(強引用類型)
long getExpireAfterWriteNanos() { return (expireAfterWriteNanos == UNSET_INT) ? DEFAULT_EXPIRATION_NANOS : expireAfterWriteNanos; }
說明:獲取超時時間,若是設置了,就是設置值,若是沒設置,默認是0
int getInitialCapacity() { return (initialCapacity == UNSET_INT) ? DEFAULT_INITIAL_CAPACITY : initialCapacity; }
說明:獲取初始化容量,若是指定了就是用指定容量,若是沒指定,默認爲16。值得注意的是,該容量是用於計算每一個Segment的容量的,並不必定是每一個Segment的容量,其具體使用的方法見LocalCache(CacheBuilder, CacheLoader)
//這裏maxWeight沒有設置值,默認爲UNSET_INT,即-1 boolean evictsBySize() { return maxWeight >= 0; }
說明:這是一個與weight相關的方法,因爲咱們沒有設置weight,因此該方法對咱們的程序沒有影響。
/** * 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的方法。
/** * 建立一個指定大小的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 + 構建相關隊列
到目前爲止,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]: