guava cache原理解析

緩存在日常開發(fā)中舉足輕重,如果你的應用對某類數(shù)據(jù)有著較高的讀取頻次,并且改動較小時那就非常適合利用緩存來提高性能稳懒。
談談 Java 中所用到的緩存,
JVM 緩存
首先是 JVM 緩存慢味,也可以認為是堆緩存场梆。其實就是創(chuàng)建一些全局變量,如 Map纯路、List 之類的容器用于存放數(shù)據(jù)或油。這樣的優(yōu)勢是使用簡單但是也有以下問題:

  • 只能顯式的寫入,清除數(shù)據(jù)驰唬。
  • 不能按照一定的規(guī)則淘汰數(shù)據(jù)顶岸,如 LRU,LFU叫编,F(xiàn)IFO 等辖佣。
  • 清除數(shù)據(jù)時的回調通知。
  • 其他一些定制功能等搓逾。

Ehcache卷谈、Guava Cache
所以出現(xiàn)了一些專門用作 JVM 緩存的開源工具出現(xiàn)了,如本文提到的 Guava Cache霞篡。
它具有上文 JVM 緩存不具有的功能世蔗,如自動清除數(shù)據(jù)端逼、多種清除算法、清除回調等污淋。但也正因為有了這些功能顶滩,這樣的緩存必然會多出許多東西需要額外維護,自然也就增加了系統(tǒng)的消耗寸爆。

分布式緩存
剛才提到的兩種緩存其實都是堆內緩存礁鲁,只能在單個節(jié)點中使用,這樣在分布式場景下就招架不住了而昨。
于是也有了一些緩存中間件救氯,如 Redis、Memcached歌憨,在分布式環(huán)境下可以共享內存着憨。

GuavaCache

GuavaCache 提供了一般我們使用緩存所需要的幾乎所有的功能,主要有:

  • 自動將entry節(jié)點加載進緩存結構中;
  • 當緩存的數(shù)據(jù)已經(jīng)超過預先設置的最大值時,使用LRU算法移除一些數(shù)據(jù)务嫡;
  • 具備根據(jù)entry節(jié)點上次被訪問或者寫入的時間來計算過期機制甲抖;
  • 緩存的key被封裝在WeakReference引用內;
  • 緩存的value被封裝在WeakReference或者SoftReference引用內;
  • 移除entry節(jié)點心铃,可以觸發(fā)監(jiān)聽器通知事件准谚;
  • 統(tǒng)計緩存使用過程中命中率/異常率/未命中率等數(shù)據(jù)。

Guava Cache其核心數(shù)據(jù)結構大體上和ConcurrentHashMap一致去扣,具體細節(jié)上會有些區(qū)別柱衔。功能上,ConcurrentMap會一直保存所有添加的元素愉棱,直到顯式地移除.相對地,Guava Cache為了限制內存占用,通常都設定為自動回收元素.在某些場景下,盡管它不回收元素,也是很有用的,因為它會自動加載緩存.

Guava Cache 官方推薦的使用場景:

  • 愿意消耗一些內存空間來提升速度唆铐;
  • 能夠預計某些key會被查詢一次以上;
  • 緩存中存放的數(shù)據(jù)總量不會超出內存容量(Guava Cache是單個應用運行時的本地緩存)奔滑。

不管性能艾岂,還是可用性來說,Guava Cache絕對是本地緩存類庫中首要推薦的工具類朋其。其提供的Builder模式的CacheBuilder生成器來創(chuàng)建緩存的方式王浴,十分方便,并且各個緩存參數(shù)的配置設置,類似于函數(shù)式編程的寫法.

Guava Cache 三種方式加載<key,value>到緩存中。分別是:

  • LoadingCache在構建緩存的時候,使用build方法內部調用CacheLoader方法加載數(shù)據(jù)
  • 在使用get方法的時候,如果緩存不存在該key或者key過期等傲宜,則調用get(K, Callable<V>)方式加載數(shù)據(jù);
  • 使用粗暴直接的方式镀琉,直接想緩存中put數(shù)據(jù)。

demo

import java.util.Date;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;

/**
 * @author tao.ke Date: 14-12-20 Time: 下午1:55
 * @version \$Id$
 */
public class CacheSample {

    private static final Logger logger = LoggerFactory.getLogger(CacheSample.class);

    // Callable形式的Cache
    private static final Cache<String, String> CALLABLE_CACHE = CacheBuilder.newBuilder()
            .expireAfterWrite(1, TimeUnit.SECONDS).maximumSize(1000).recordStats()
            .removalListener(new RemovalListener<Object, Object>() {
                @Override
                public void onRemoval(RemovalNotification<Object, Object> notification) {
                    logger.info("Remove a map entry which key is {},value is {},cause is {}.", notification.getKey(),
                            notification.getValue(), notification.getCause().name());
                }
            }).build();

    // CacheLoader形式的Cache
    private static final LoadingCache<String, String> LOADER_CACHE = CacheBuilder.newBuilder()
            .expireAfterAccess(1, TimeUnit.SECONDS).maximumSize(1000).recordStats().build(new CacheLoader<String, String>() {
                @Override
                public String load(String key) throws Exception {
                    return key + new Date();
                }
            });

    public static void main(String[] args) throws Exception {

        int times = 4;
        while (times-- > 0) {

            Thread.sleep(900);

            String valueCallable = CALLABLE_CACHE.get("key", new Callable<String>() {
                @Override
                public String call() throws Exception {
                    return "key" + new Date();
                }
            });

            logger.info("Callable Cache ----->>>>> key is {},value is {}", "key", valueCallable);
            logger.info("Callable Cache ----->>>>> stat miss:{},stat hit:{}",CALLABLE_CACHE.stats().missRate(),CALLABLE_CACHE.stats().hitRate());

            String valueLoader = LOADER_CACHE.get("key");

            logger.info("Loader Cache ----->>>>> key is {},value is {}", "key", valueLoader);
            logger.info("Loader Cache ----->>>>> stat miss:{},stat hit:{}",LOADER_CACHE.stats().missRate(),LOADER_CACHE.stats().hitRate());

        }

    }
}

上述代碼爽撒,簡單的介紹了Guava Cache 的使用,給了兩種加載構建Cache的方式响蓉。在Guava Cache對外提供的方法中硕勿, recordStats和removalListener是兩個很有趣的接口,可以很好的幫我們完成統(tǒng)計功能和Entry移除引起的監(jiān)聽觸發(fā)功能枫甲。

雖然在Guava Cache對外方法接口中提供了豐富的特性源武,但是如果我們在實際的代碼中不是很有需要的話,建議不要設置這些屬性想幻,因為會額外占用內存并且會多一些處理計算工作粱栖,不值得。

Guava Cache 前置知識

Guava Cache就是借鑒Java的ConcurrentHashMap的思想來實現(xiàn)一個本地緩存脏毯,但是它內部代碼實現(xiàn)的時候闹究,還是有很多非常精彩的設計實現(xiàn),

Builder模式

設計模式之 Builder模式 在Guava中很多地方得到的使用食店。Builder模式是將一個復雜對象的構造與其對應配置屬性表示的分離渣淤,也就是可以使用基本相同的構造過程去創(chuàng)建不同的具體對象。
具體參照筆者的這篇Builder模式

java 對象引用

當虛擬機執(zhí)行時吉嫩,遇到一條new指令時价认,首先會去檢查這個指令在常量池中是否已經(jīng)存在該類對應的符號引用,并且檢查這個符號引用對應的類是否已經(jīng)被加載自娩,解析和初始化用踩。如果沒有,則執(zhí)行相應的類加載過程忙迁。

然后虛擬機為新的對象分配內存脐彩。虛擬機根據(jù)我們配置的垃圾收集器規(guī)則采取不同的分配方式,包括:指針碰撞分配方式和空閑列表分配方式动漾。

內存分配完成之后丁屎,開始執(zhí)行init方法。init方法會按照代碼的指定過程來初始化旱眯,對一些類屬性進行賦值晨川。

然后,我們需要訪問這個對象删豺,怎么辦共虑?在Java運行時內存區(qū)域模型中,線程擁有一個虛擬機棧呀页,這個棧會有一個本地方法表妈拌,這個表內部就會存放一個引用地址,如下圖所示(HotSpot虛擬機采用這種方式,還有另外一種形式這里不做介紹):


image.png

在JDK 1.2之前尘分,Java中關于引用的定義是:如果reference類型的數(shù)據(jù)中存儲的數(shù)值表示的是另外一塊內存的起始地址猜惋,就說明這塊內存稱為引用。

這種定義表明對象只有兩種:被引用的對象和沒有被引用的對象培愁。這種方式對于垃圾收集GC來說著摔,效果并不是很好,因為很多對象劃為為被引用和非被引用都不是很重要定续,這種現(xiàn)象就無法劃分谍咆。垃圾收集的時候,就無法更好更精準的劃為可GC的對象私股。

因此摹察,在JDK 1.2之后,Java對引用的概念進行擴展倡鲸,有如下四種類型的引用(按強度排序):

* 強引用(Strong Reference)

* 軟引用(SoftReference)

* 弱引用(WeakReference)

* 虛引用(PhantomReference)
  • 強引用:強引用在程序代碼中隨處可見供嚎,十分普遍。比如: Object object = new Object() 旦签,這類引用只要還存在查坪,垃圾收集器就永遠不會回收掉這類引用的對象。
  • 軟引用:軟引用用來描述一些雖然有用但是并不是必須的對象宁炫。對于軟引用關聯(lián)的對象偿曙,在系統(tǒng)將可能發(fā)生內存溢出異常之前,垃圾收集器將會把這些引用的對象進行第二次回收羔巢。只有這次垃圾回收還沒有足夠的內存的時候望忆,才會拋出內存溢出異常。
  • 弱引用:弱引用是一種比軟引用強度還要弱的引用竿秆,因此這些引用的對象也是非必須的启摄。但是,對于弱引用的對象只能生存到下一次垃圾回收發(fā)生之前幽钢。當垃圾收集工作開始后歉备,無論當前的內存是否夠用,都會把這些弱引用的對象回收掉匪燕。
  • 虛引用:虛引用是最弱的一種引用蕾羊。一個對象是否被虛引用關聯(lián),完全不會對其生存時間構成影響帽驯,也無法通過虛引用獲得一個對象實例龟再。為一個對象設置虛引用關聯(lián)的唯一目的就是能在這個對象被收集器回收時收到一個系統(tǒng)通知

關于引用,最典型的使用就是對HashMap的自定義開發(fā)尼变,包括JDK內部也存在利凑。

  • Strong Reference—> HashMap:默認情況下,HashMap使用的引用就是強引用,也就是說垃圾收集的時候哀澈,Map中引用的對象不會被GC掉牌借。

  • Weak Reference—> WeakHashMap:JDK中還有一種基于引用類型實現(xiàn)的HashMap,WeakHashMap日丹。當節(jié)點的key不在被使用的時候走哺,該entry就會被自動回收掉。因此哲虾,對于一個mapping映射,不能保證接下來的GC不會把這個entry回收掉择示。

  • Soft Reference—> SoftHashMap:在JDK中沒有提供基于軟引用實現(xiàn)的HashMap束凑,原因可能是一般大家都不能期待出現(xiàn)內存溢出,而當出現(xiàn)內存溢出栅盲,一點點的軟引用GC余下的內存空間汪诉,肯定不會起到關鍵作用。但是谈秫,雖然不廣泛扒寄,在aspectj提供的ClassLoaderRepository類中實現(xiàn)了SoftHashMap,作為一個基于ClassLoader字節(jié)碼實現(xiàn)的方法拟烫,在OOM的時候该编,顯然需要考慮通過GC釋放內存空間,并且SoftHashMap在內部是作為緩存使用硕淑。

具體可參照筆者這篇java四種引用

JMM可見性

什么叫可見性课竣?

可見性就是,當程序中一個線程修改了某個全局共享變量的值之后置媳,其他使用該值的線程都可以獲知于樟,在隨后他們讀該共享變量的時候,查詢的都是最新的改改修改的值拇囊。

一個線程上修改共享變量迂曲,這個變量的最新的值不會立刻寫入到共享內存中,還是暫時存放在線程本地緩存寥袭,然后某一時刻觸發(fā)寫入到共享內存中路捧。可見性就是纠永,當我們對共享變量修改的時候鬓长,立刻把新值同步到主內存中,然后該變量被讀的時候從主內存獲取最新的值確保所有對該變量的讀取操作尝江,總是獲取最新最近修改的值涉波。

為什么會有可見性問題?

學過計算機組成原理的同學都知道,在現(xiàn)代CPU結構中啤覆,存在多級緩存架構苍日,如下圖所示:


image.png

同樣,在Java虛擬機中分為兩種內存:

  • 主內存(Main Memory):所有線程共享的內存區(qū)域窗声,虛擬機內存的一部分相恃。
  • 工作內存(Working Memory):線程自己操作的內存區(qū)域,線程直接無法訪問對方的工作內存區(qū)域笨觅。

之所以分為兩部分內存區(qū)域拦耐,原因和CPU很類似。為了線程可以快速訪問操作變量见剩,當線程全部直接操作共享內存杀糯,則會導致大量線程之間競爭等問題出現(xiàn),影響效率苍苞。
關于Java中線程固翰,工作內存,主內存之間的交互關系如下圖

image.png

為了保證共享變量可見性羹呵,除了上篇博文中介紹的volatile之外骂际,還有synchronized和final關鍵字。

synchronized:執(zhí)行synchronized代碼塊時冈欢,在對變量執(zhí)行unlock操作之前歉铝,一定會把此變量寫入到主內存中。final:該關鍵字修飾的變量在構造函數(shù)中初始化完成之后(不考慮指針逃逸涛癌,變量初始化一半的問題)犯戏,其他線程就可以看到這個final變量的值,并且由于變量不能修改拳话,所以能確毕确耍可見性。

指令重排序

重排序通常是編譯器或者運行時環(huán)境為了優(yōu)化程序性能而采取的對指令進行重新排序執(zhí)行的一種手段弃衍。重排序分為兩類:編譯期重排序和運行期重排序呀非,分別對應編譯時和運行時環(huán)境。

在運行時不同的微指令在不同的執(zhí)行單元中同時執(zhí)行镜盯,而且每個執(zhí)行單元都全速運行岸裙。只要當前微指令所需要的數(shù)據(jù)就緒,而且有空閑的執(zhí)行單元速缆,微指令就可以立即執(zhí)行降允,有時甚至可以跳過前面還未就緒的微指令。通過這種方式艺糜,需要長時間運行的操作不會阻塞后面的操作剧董,流水線阻塞帶來的損失被極大的減小了幢尚。

運行期JVM會對指令進行重排序以提高程序性能,當然其會通過happens-before原則保證順序執(zhí)行語義翅楼,也就是不會隨便對代碼指令進行重排序尉剩。

class ReorderExample {
int a = 0;
boolean flag = false;

public void writer() {
    a = 1;                   //1
    flag = true;             //2
}

Public void reader() {
    if (flag) {                //3
        int i =  a * a;        //4
        ……
    }
}
}

上述的代碼會造成很多的不同結果,由于數(shù)據(jù)的可見性問題毅臊,或者就是重排序理茎。比如重排序后執(zhí)行順序如下,則會存在問題管嬉。

image.png

可參考volatile

鎖細化

這兩年十分火的用于線程間通信的高性能消息組件皂林,其雖然有很多創(chuàng)新的設計,但是很多優(yōu)化的基本就是蚯撩,鎖細化式撼,此外,在Linux內核2.6之后采用的RCU鎖機制求厕,本質上也是鎖粒度細化。 在Java語言中扰楼,最經(jīng)典的鎖細化提高多線程并發(fā)性能的案例呀癣,就是ConcurrentHashMap,其采用多個segment弦赖,每個segment對應一個鎖项栏,來分散全局鎖帶來的性能損失。從而蹬竖,當我們put某一個entry的時候沼沈,在實現(xiàn)的時候,一般只需要擁有某一個segment鎖就可以完成币厕。

關于普通的HashTable結構和ConcurrentHashMap結構列另,借用一張圖來說明:

image.png

從結構上,可以很顯而易見的看出兩者的區(qū)別旦装。所以页衙,就鎖這個層面上,concurrentHashMap就會比HashTable性能好阴绢。

Guava ListenableFuture接口

可參照ListenableFuture

CacheBuilder實現(xiàn)

寫過Cache的店乐,或者其他一些工具類的同學知道,為了讓工具類更靈活呻袭,我們需要對外提供大量的參數(shù)配置給使用者設置眨八,雖然這帶有一些好處,但是由于參數(shù)太多左电,使用者開發(fā)構造對象的時候過于繁雜廉侧。

上面提到過參數(shù)配置過多页响,可以使用Builder模式。Guava Cache也一樣伏穆,它為我們提供了CacheBuilder工具類來構造不同配置的Cache實例拘泞。但是,和本文上面提到的構造器實現(xiàn)有點不一樣枕扫,它構造器返回的是另外一個對象陪腌,因此,這意味著在實現(xiàn)的時候烟瞧,對象構造函數(shù)需要有Builder參數(shù)提供配置屬性诗鸭。

CacheBuilder構造LocalCache實現(xiàn)

首先,我們先看看Cache的構造函數(shù):

/**
 * 從builder中獲取相應的配置參數(shù)参滴。 
 */

LocalCache(CacheBuilder<? super K, ? super V> builder, @Nullable CacheLoader<? super K, V> loader) {
    concurrencyLevel = Math.min(builder.getConcurrencyLevel(), MAX_SEGMENTS);

    keyStrength = builder.getKeyStrength();
    valueStrength = builder.getValueStrength();

    keyEquivalence = builder.getKeyEquivalence();
    valueEquivalence = builder.getValueEquivalence();

    maxWeight = builder.getMaximumWeight();
    weigher = builder.getWeigher();
    expireAfterAccessNanos = builder.getExpireAfterAccessNanos();
    expireAfterWriteNanos = builder.getExpireAfterWriteNanos();
    refreshNanos = builder.getRefreshNanos();

    removalListener = builder.getRemovalListener();
    removalNotificationQueue = (removalListener == NullListener.INSTANCE) ? LocalCache
                .<RemovalNotification<K, V>> discardingQueue() : new ConcurrentLinkedQueue<RemovalNotification<K, V>>();

    ticker = builder.getTicker(recordsTime());
    entryFactory = EntryFactory.getFactory(keyStrength, usesAccessEntries(), usesWriteEntries());
    globalStatsCounter = builder.getStatsCounterSupplier().get();
    defaultLoader = loader;

    int initialCapacity = Math.min(builder.getInitialCapacity(), MAXIMUM_CAPACITY);
    if (evictsBySize() && !customWeigher()) {
        initialCapacity = Math.min(initialCapacity, (int) maxWeight);
    }

    //.......
}

從構造函數(shù)可以看到强岸,Cache的所有參數(shù)配置都是從Builder對象中獲取的,Builder完成了作為該模式最典型的應用砾赔,多配置參數(shù)構建對象蝌箍。

在Cache中只提供一個構造函數(shù),但是在上面代碼示例中暴心,我們演示了兩種構建緩存的方式:自動加載妓盲;手動加載。那么专普,一般會存在一個完成兩者之間的過渡adapter組件悯衬,接下來看看Builder在內部是如何完成創(chuàng)建緩存對象過程的。

在LocalCache中確實提供了兩種過渡類檀夹,一個是支持自動加載value的LocalLoadingCache 和只能在鍵值找不到的時候手動調用獲取值方法的LocalManualCache筋粗。

LocalManualCache實現(xiàn)

  static class LocalManualCache<K, V> implements Cache<K, V>, Serializable {
        final LocalCache<K, V> localCache;

        LocalManualCache(CacheBuilder<? super K, ? super V> builder) {
            this(new LocalCache<K, V>(builder, null));
        }

        private LocalManualCache(LocalCache<K, V> localCache) {
            this.localCache = localCache;
        }

        // Cache methods

        @Override
        @Nullable
        public V getIfPresent(Object key) {
            return localCache.getIfPresent(key);
        }

        @Override
        public V get(K key, final Callable<? extends V> valueLoader) throws ExecutionException {
            checkNotNull(valueLoader);
            return localCache.get(key, new CacheLoader<Object, V>() {
                @Override
                public V load(Object key) throws Exception {
                    return valueLoader.call();
                }
            });
        }

        //......

        @Override
        public CacheStats stats() {
            SimpleStatsCounter aggregator = new SimpleStatsCounter();
            aggregator.incrementBy(localCache.globalStatsCounter);
            for (Segment<K, V> segment : localCache.segments) {
                aggregator.incrementBy(segment.statsCounter);
            }
            return aggregator.snapshot();
        }

        // Serialization Support

        private static final long serialVersionUID = 1;

        Object writeReplace() {
            return new ManualSerializationProxy<K, V>(localCache);
        }
    }

從代碼實現(xiàn)看出實際上是一個adapter組件,并且絕大部分實現(xiàn)都是直接調用LocalCache的方法炸渡,或者加一些參數(shù)判斷和聚合娜亿。在它核心的構造函數(shù)中,就是直接調用LocalCache構造函數(shù)偶摔,對于loader對象直接設null值暇唾。

LocalLoadingCache實現(xiàn)
LocalLoadingCache實現(xiàn)繼承了LocalManualCache 類,其主要對get相關方法做了重寫辰斋。

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

        // LoadingCache methods

        @Override
        public V get(K key) throws ExecutionException {
            return localCache.getOrLoad(key);
        }

        @Override
        public V getUnchecked(K key) {
            try {
                return get(key);
            } catch (ExecutionException e) {
                throw new UncheckedExecutionException(e.getCause());
            }
        }

        @Override
        public ImmutableMap<K, V> getAll(Iterable<? extends K> keys) throws ExecutionException {
            return localCache.getAll(keys);
        }

        @Override
        public void refresh(K key) {
            localCache.refresh(key);
        }

        @Override
        public final V apply(K key) {
            return getUnchecked(key);
        }

        // Serialization Support

        private static final long serialVersionUID = 1;

        @Override
        Object writeReplace() {
            return new LoadingSerializationProxy<K, V>(localCache);
        }
    }
}

提供了這些adapter類之后策州,builder類就可以創(chuàng)建LocalCache,如下:

 // 獲取value可以通過key計算出
   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);
    }

    // 手動加載
    public <K1 extends K, V1 extends V> Cache<K1, V1> build() {
        checkWeightWithWeigher();
        checkNonLoadingCache();
        return new LocalCache.LocalManualCache<K1, V1>(this);
    }

CacheBuilder參數(shù)設置

CacheBuilder在為我們提供了構造一個Cache對象時宫仗,會構造各個成員對象的初始值(默認值)够挂。了解這些默認值,對于我們分析Cache源碼實現(xiàn)時藕夫,一些判斷條件的設置原因孽糖,還是很有用的枯冈。

初始參數(shù)值設置
在ConcurrentHashMap中,我們知道有個并發(fā)水平(CONCURRENCY_LEVEL)办悟,這個參數(shù)決定了其允許多少個線程并發(fā)操作修改該數(shù)據(jù)結構尘奏。這是因為這個參數(shù)是最后map使用的segment個數(shù),而每個segment對應一個鎖病蛉,因此炫加,對于一個map來說,并發(fā)環(huán)境下铺然,理論上最大可以有segment個數(shù)的線程同時安全地操作修改數(shù)據(jù)結構俗孝。那么是不是segment的值可以設置很大呢?顯然不是魄健,要記住維護一個鎖的成本還是挺高的赋铝,此外如果涉及全表操作,那么性能就會非常不好了沽瘦。

其他一些初始參數(shù)值的設置如下所示:

 private static final int DEFAULT_INITIAL_CAPACITY = 16; // 默認的初始化Map大小
    private static final int DEFAULT_CONCURRENCY_LEVEL = 4; // 默認并發(fā)水平
    private static final int DEFAULT_EXPIRATION_NANOS = 0; // 默認超時
    private static final int DEFAULT_REFRESH_NANOS = 0; // 默認刷新時間
    static final int UNSET_INT = -1;
    boolean strictParsing = true;
    int initialCapacity = UNSET_INT;
    int concurrencyLevel = UNSET_INT;
    long maximumSize = UNSET_INT;
    long maximumWeight = UNSET_INT;
    long expireAfterWriteNanos = UNSET_INT;
    long expireAfterAccessNanos = UNSET_INT;
    long refreshNanos = UNSET_INT;

初始對象引用設置
在Cache中革骨,我們除了超時時間,鍵值引用屬性等設置外析恋,還關注命中統(tǒng)計情況苛蒲,這就需要統(tǒng)計對象來工作。CacheBuilder提供了初始的null 統(tǒng)計對象和空統(tǒng)計對象绿满。

/**
     * 默認空的緩存命中統(tǒng)計類
     */
    static final Supplier<? extends StatsCounter> NULL_STATS_COUNTER = Suppliers.ofInstance(new StatsCounter() {
        
        //......省略空override

        @Override
        public CacheStats snapshot() {
            return EMPTY_STATS;
        }
    });
    static final CacheStats EMPTY_STATS = new CacheStats(0, 0, 0, 0, 0, 0);// 初始狀態(tài)的統(tǒng)計對象

    /**
     * 系統(tǒng)實現(xiàn)的簡單的緩存狀態(tài)統(tǒng)計類
     */
    static final Supplier<StatsCounter> CACHE_STATS_COUNTER = new Supplier<StatsCounter>() {
        @Override
        public StatsCounter get() {
            return new SimpleStatsCounter();//這里構造簡單地統(tǒng)計類實現(xiàn)
        }
    };

    /**
     * 自定義的空RemovalListener,監(jiān)聽到移除通知窟扑,默認空處理喇颁。
     */
    enum NullListener implements RemovalListener<Object, Object> {
        INSTANCE;

        @Override
        public void onRemoval(RemovalNotification<Object, Object> notification) {
        }
    }

    /**
     * 默認權重類,任何對象的權重均為1
     */
    enum OneWeigher implements Weigher<Object, Object> {
        INSTANCE;

        @Override
        public int weigh(Object key, Object value) {
            return 1;
        }
    }

    static final Ticker NULL_TICKER = new Ticker() {
        @Override
        public long read() {
            return 0;
        }
    };

    /**
     * 默認的key等同判斷
     * @return
     */
    Equivalence<Object> getKeyEquivalence() {
        return firstNonNull(keyEquivalence, getKeyStrength().defaultEquivalence());
    }

    /**
     * 默認value的等同判斷
     * @return
     */
    Equivalence<Object> getValueEquivalence() {
        return firstNonNull(valueEquivalence, getValueStrength().defaultEquivalence());
    }

    /**
     * 默認的key引用
     * @return
     */
    Strength getKeyStrength() {
        return firstNonNull(keyStrength, Strength.STRONG);
    }

    /**
     * 默認為Strong 屬性的引用
     * @return
     */
    Strength getValueStrength() {
        return firstNonNull(valueStrength, Strength.STRONG);
    }

    <K1 extends K, V1 extends V> Weigher<K1, V1> getWeigher() {
        return (Weigher<K1, V1>) Objects.firstNonNull(weigher, OneWeigher.INSTANCE);
    }

其中嚎货,在我們不設置緩存中鍵值引用的情況下橘霎,默認都是采用強引用及相對應的屬性策略來初始化的。此外殖属,在上面代碼中還可以看到姐叁,統(tǒng)計類SimpleStatsCounter是一個簡單的實現(xiàn)。里面主要是簡單地緩存累加洗显,此外由于多線程下Long類型的線程非安全性外潜,所以也進行了一下封裝,下面給出命中率的實現(xiàn):

public static final class SimpleStatsCounter implements StatsCounter {

    private final LongAddable hitCount = LongAddables.create();
    private final LongAddable missCount = LongAddables.create();
    private final LongAddable loadSuccessCount = LongAddables.create();
    private final LongAddable loadExceptionCount = LongAddables.create();
    private final LongAddable totalLoadTime = LongAddables.create();
    private final LongAddable evictionCount = LongAddables.create();

    public SimpleStatsCounter() {}
    @Override
    public void recordHits(int count) {
      hitCount.add(count);
    }
    @Override
    public CacheStats snapshot() {
      return new CacheStats(
          hitCount.sum());
    }

    /**
     * Increments all counters by the values in {@code other}.
     */
    public void incrementBy(StatsCounter other) {
      CacheStats otherStats = other.snapshot();
      hitCount.add(otherStats.hitCount());
    }
}

LocalCache實現(xiàn)
在設計實現(xiàn)上挠唆,LocalCache的并發(fā)策略和concurrentHashMap的并發(fā)策略是一致的处窥,也是根據(jù)分段鎖來提高并發(fā)能力,分段鎖可以很好的保證 并發(fā)讀寫的效率。因此玄组,該map支持非阻塞讀和不同段之間并發(fā)寫

LoacalCache使用LRU頁面替換算法滔驾,是因為該算法簡單谒麦,并且有很高的命中率,以及O(1)的時間復雜度哆致。需要說明的是绕德, LRU算法是基于頁面而不是全局實現(xiàn)的,所以可能在命中率上不如全局LRU算法摊阀,但是應該基本相似耻蛇。,是因為在計算機專業(yè)課程上驹溃,CPU城丧,操作系統(tǒng),算法上豌鹤,基本上都介紹過分頁導致優(yōu)化效果的提升亡哄。

總體數(shù)據(jù)結構

LocalCache的數(shù)據(jù)結構和ConcurrentHashMap一樣,都是采用分segment來細化管理HashMap中的節(jié)點Entry布疙。借用ConcurrentHashMap的數(shù)據(jù)結構圖來說明Cache的實現(xiàn):

image.png

從圖中可以直觀看到cache是以segment粒度來控制并發(fā)get和put等操作的蚊惯,接下來首先看我們的LocalCache是如何構造這些segment段的,繼續(xù)上面初始化localCache構造函數(shù)的代碼:

// 找到大于并發(fā)水平的最小2的次方的值灵临,作為segment數(shù)量
        int segmentShift = 0;
        int segmentCount = 1;
        while (segmentCount < concurrencyLevel && (!evictsBySize() || segmentCount * 20 <= maxWeight)) {
            ++segmentShift;
            segmentCount <<= 1;
        }
        this.segmentShift = 32 - segmentShift;//位 偏移數(shù)
        segmentMask = segmentCount - 1;//mask碼

        this.segments = newSegmentArray(segmentCount);// 構造數(shù)據(jù)數(shù)組截型,如上圖所示
        //獲取每個segment初始化容量,并且保證大于等于map初始容量
        int segmentCapacity = initialCapacity / segmentCount;
        if (segmentCapacity * segmentCount < initialCapacity) {
            ++segmentCapacity;
        }

        //段Size 必須為2的次數(shù)儒溉,并且剛剛大于段初始容量
        int segmentSize = 1;
        while (segmentSize < segmentCapacity) {
            segmentSize <<= 1;
        }

        // 權重設置宦焦,確保權重和==map權重
        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());
            }
        }

基本上都是基于2的次數(shù)來設置大小的,顯然基于移位操作比普通計算操作速度要快顿涣。此外波闹,對于最大權重分配到段權重的設計上,很特殊涛碑。為什么呢精堕?為了保證兩者能夠相等(maxWeight==sumAll(maxSegmentWeight)),對于remainder前面的segment maxSegmentWeight的值比remainder后面的權重值大1,這樣保證最后值相等蒲障。

map get 方法

    @Override
    @Nullable
    public V get(@Nullable Object key) {
        if (key == null) {
            return null;
        }
        int hash = hash(key);
        return segmentFor(hash).get(key, hash);
    }


首先check key是否為null歹篓,然后計算hash值,定位到對應的segment揉阎,執(zhí)行segment實例擁有的get方法獲取對應的value值

map put 方法

@Override
    public V put(K key, V value) {
        checkNotNull(key);
        checkNotNull(value);
        int hash = hash(key);
        return segmentFor(hash).put(key, hash, value, false);
    }

和get方法一樣庄撮,也是先check值,然后計算key的hash值毙籽,然后定位到對應的segment段重窟,執(zhí)行段實例的put方法,將鍵值存入map中惧财。

map isEmpty 方法

 @Override
    public boolean isEmpty() {
        long sum = 0L;
        Segment<K, V>[] segments = this.segments;
        for (int i = 0; i < segments.length; ++i) {
            if (segments[i].count != 0) {
                return false;
            }
            sum += segments[i].modCount;
        }

        if (sum != 0L) { // recheck unless no modifications
            for (int i = 0; i < segments.length; ++i) {
                if (segments[i].count != 0) {
                    return false;
                }
                sum -= segments[i].modCount;
            }
            if (sum != 0L) {
                return false;
            }
        }
        return true;
    }

判斷Cache是否為空巡扇,就是分別判斷每個段segment是否都為空扭仁,但是由于整體是在并發(fā)環(huán)境下進行的,也就是說存在對一個segment并發(fā)的增加和移除元素的時候厅翔,而我們此時正在check其他segment段乖坠。

上面這種情況,決定了我們不能夠獲得任何一個時間點真實段狀態(tài)的情況刀闷。因此熊泵,上面的代碼引入了sum變量來計算段modCount變更情況。modCount表示改變segment大小size的更新次數(shù)甸昏,這個在批量讀取方法期間保證它們可以看到一致性的快照顽分。需要注意,這里先獲取count施蜜,該值是volatile卒蘸,因此modCount通常都可以在不需要一致性控制下,獲得當前segment最新的值.

在判斷如果在第一次check的時候翻默,發(fā)現(xiàn)segment發(fā)生了數(shù)據(jù)結構級別變更缸沃,則會進行recheck,就是在每個modCount下修械,段仍然是空的趾牧,則判斷該map為空。如果發(fā)現(xiàn)這期間數(shù)據(jù)結構發(fā)生變化肯污,則返回非空判斷翘单。

來看一張LocalCache的數(shù)據(jù)結構圖:


image.png

LocalCache類似ConcurrentHashMap采用了分段策略,通過減小鎖的粒度來提高并發(fā)蹦渣,LocalCache中數(shù)據(jù)存儲在Segment[]中县恕,每個segment又包含5個隊列和一個table,這個table是自定義的一種類數(shù)組的結構,每個元素都包含一個ReferenceEntry<k,v>鏈表剂桥,指向next entry。

這些隊列属提,前2個是key权逗、value引用隊列用以加速GC回收亩码,后3個隊列記錄用戶的寫記錄绑警、訪問記錄、高頻訪問順序隊列用以實現(xiàn)LRU算法耍攘。AtomicReferenceArray是JUC包下的Doug Lea老李頭設計的類:一組對象引用恕酸,其中元素支持原子性更新堪滨。

LoadingCache內部采用分段鎖控實現(xiàn)寫并發(fā),每個段(Segment)內有如下幾個主要的數(shù)據(jù)結構:

  • hash表: 最基本的數(shù)據(jù)結構蕊温,hash數(shù)組袱箱,采用AtomicReferenceArray來實現(xiàn)遏乔,這里主要是利用atmoic array的寫操作對讀的可見性 (unsafe.putObjectVolatile/ unsafe.getObjectVolatile)。hash沖突采用最基本的單鏈表來解決发笔,所以數(shù)組中元素ReferenceEntry有個指向鏈表下一個元素的指針盟萨。

  • key引用隊列和value引用隊列 如果在build緩存時啟用了key/value的引用封裝,那么在創(chuàng)建segment時就會初始化對應的這兩個隊列了讨,用于接收gc回收key/value對象的通知捻激,隊列中元素是引用key/value的Reference對象。顯然ReferenceQueue是線程安全的前计,隊列的生成者是jvm的gc線程胞谭,消費者是LoadingCache自身,消費的時機前面也提過了男杈。消費做的事情就是清理:即hash表中對應key/value相關的entry從hash表中刪除(具體代碼見drainReferenceQueues方法)丈屹。

  • 最近寫隊列 如果build緩存時設置了元素寫后過期時間,那么創(chuàng)建segment時就會初始化這個WriteQueue势就,WriteQueue的實現(xiàn)非常簡單泉瞻,就是一個帶頭節(jié)點的雙向循環(huán)鏈表,而且沒有考慮任何并發(fā)訪問苞冯。節(jié)點對象就是ReferenceEntry本身袖牙。注意到hash表里的散列鏈表節(jié)點也是ReferenceEntry,這是一個很常見的技巧:即一個節(jié)點對象可能會同時屬于多個鏈表中舅锄,不同的鏈表使用不同的前后節(jié)點指針鞭达,這個技巧的好處在于給定一個節(jié)點entry對象,所有鏈表都可以做到常數(shù)時間的查找和刪除(jdk里的LinkedHashMap實現(xiàn)也采用了這個技巧)皇忿。由于寫操作都會在鎖保護進行畴蹭,因此WriteQueue無需是線程安全的。

  • 最近讀隊列(recencyQueue) 和 最近訪問隊列(accessQueue/最近LRU隊列) 之所以把這二者放在一起鳍烁,是因為他們密切相關叨襟。如果在build緩存時設置了緩存的最大容量或者是為緩存元素設置了訪問后過期時間,那么在初始化segment時這兩個隊列就會被初始化幔荒。其中最近讀隊列是采用的是jdk中線程安全糊闽、支持高并發(fā)讀寫的ConcurrentLinkedQueue,隊列中存儲的元素是ReferenceEntry(注意這和節(jié)點是ReferentEntry本身的WriteQueue的區(qū)別)爹梁;最近訪問隊列采用的則是LoadingCache自己實現(xiàn)的AccessQueue右犹,AccessQueue的實現(xiàn)和前面的WriteQueue基本一致,節(jié)點是ReferenceEntry姚垃、非線程安全念链。那么問題來了:為啥需要兩個隊列來實現(xiàn)LRU功能。要回答這個問題首先得明確在LoadingCache場景下一個LRU隊列需求有哪些:1)緩存的場景基本都是讀多寫少,LoadingCache的讀操作要做到的是高性能掂墓、lock-free的讀谦纱,這樣就會有多個線程同時讀緩存,意味著LRU隊列支持多線程高并發(fā)寫入(調整元素的LRU隊列中的訪問順序) 2)LoadingCache中元素可能會因為過期梆暮、容量限制服协、被gc回收等原因被淘汰出緩存,意味著需要從LRU隊列中高效刪除元素啦粹。因此我們需要的是一個支持多線程并發(fā)訪問的偿荷、常數(shù)時間刪除元素的隊列實現(xiàn)。顯然一個ConcurrentLinkedQueue不能同時滿足這兩個需求唠椭,Guava給的解就是再增加一個簡單的AccessQueue做到常數(shù)時間刪除元素跳纳。具體來說:
    每次無鎖的讀操作都會去寫而且只會寫最近讀隊列(將entry無腦入隊,見方法:recordRead)
    每次鎖保護下寫操作都會涉及到最近訪問隊列的讀寫贪嫂,比如每次向緩存新增元素都會做幾次清理工作寺庄,清理就需要讀accessQueue(淘汰掉隊頭的元素,見方法expireEntries力崇、evictEntries)斗塘;每次向緩存新增元素成功后記錄元素寫操作,記錄會寫accessQueue(加到隊尾亮靴,見方法recordWrite)馍盟。每次訪問accessQueue前都需要先排干最近讀隊列至accessQueue中(按先進先出順序,相當于批量調整accessQueue中元素順序)茧吊,然后再去進行accessQueue的讀或者寫操作贞岭,以盡量保證accessQueue中元素順序和真實的最近訪問順序一致(見方法:drainRecencyQueue)

最后是ReferenceEntry:引用數(shù)據(jù)存儲接口,默認強引用搓侄,類圖如下:

image.png
引用數(shù)據(jù)結構

在介紹Segment數(shù)據(jù)結構之前瞄桨,先講講Cache中引用的設計。

在Guava Cache 中讶踪,主要使用三種引用類型芯侥,分別是:STRONG引用,SOFT引用 乳讥,WEAK引用柱查。和Map不同,在Cache中雏婶,使用ReferenceEntry來封裝鍵值對,并且對于值來說白指,還額外實現(xiàn)了ValueReference引用對象來封裝對應Value對象留晚。

ReferenceEntry節(jié)點結構

為了支持各種不同類型的引用,以及不同過期策略,這里構造了一個ReferenceEntry節(jié)點結構错维。通過下面的節(jié)點數(shù)據(jù)結構奖地,可以清晰的看到緩存大致操作流程。

  /**
     * 引用map中一個entry節(jié)點赋焕。
     *
     * 在map中得entries節(jié)點有下面幾種狀態(tài):
     * valid:-live:設置了有效的key/value;-loading:加載正在處理中....
     * invalid:-expired:時間過期(但是key/value可能仍然設置了)参歹;Collected:key/value部分被垃圾收集了,但是還沒有被清除隆判;
     * -unset:標記為unset犬庇,表示等待清除或者重新使用。
     *
     */
    interface ReferenceEntry<K, V> {
        /**
         * 從entry中返回value引用
         */
        ValueReference<K, V> getValueReference();

        /**
         * 為entry設置value引用
         */
        void setValueReference(ValueReference<K, V> valueReference);

        /**
         * 返回鏈中下一個entry(解決hash碰撞存在鏈表)
         */
        @Nullable
        ReferenceEntry<K, V> getNext();

        /**
         * 返回entry的hash
         */
        int getHash();

        /**
         * 返回entry的key
         */
        @Nullable
        K getKey();

        /*
         * Used by entries that use access order. Access entries are maintained in a doubly-linked list. New entries are
         * added at the tail of the list at write time; stale entries are expired from the head of the list.
         */

        /**
         * 返回該entry最近一次被訪問的時間ns
         */
        long getAccessTime();

        /**
         * 設置entry訪問時間ns.
         */
        void setAccessTime(long time);

        /**
         * 返回訪問隊列中下一個entry
         */
        ReferenceEntry<K, V> getNextInAccessQueue();

        /**
         * Sets the next entry in the access queue.
         */
        void setNextInAccessQueue(ReferenceEntry<K, V> next);

        /**
         * Returns the previous entry in the access queue.
         */
        ReferenceEntry<K, V> getPreviousInAccessQueue();

        /**
         * Sets the previous entry in the access queue.
         */
        void setPreviousInAccessQueue(ReferenceEntry<K, V> previous);

        // ...... 省略write隊列相關方法侨嘀,和access一樣
    }

從上面代碼可以看到除了和Map一樣臭挽,有key、value咬腕、hash和next四個屬性之外欢峰,還有訪問和寫更新兩個雙向鏈表隊列,以及entry的最近訪問時間和最近更新時間涨共。顯然纽帖,多出來的屬性就是為了支持緩存必須要有的過期機制。
從上面的代碼可以看出cache支持的LRU機制實際上是建立在segment上的举反,也就是基于頁的替換機制懊直。

關于訪問隊列數(shù)據(jù)結構,其實質就是一個雙向的鏈表照筑。當節(jié)點被訪問的時候吹截,這個節(jié)點將會移除,然后把這個節(jié)點添加到鏈表的結尾凝危。創(chuàng)建不同類型的ReferenceEntry由其枚舉工廠類EntryFactory來實現(xiàn)波俄,它根據(jù)key的Strength類型、是否使用accessQueue蛾默、是否使用writeQueue來決定不同的EntryFactry實例懦铺,并通過它創(chuàng)建相應的ReferenceEntry實例

ValueReference結構
同樣為了支持Cache中各個不同類型的引用,其對Value類型進行再封裝支鸡,支持引用冬念。看看其內部數(shù)據(jù)屬性:

  /**
     * A reference to a value.
     */
    interface ValueReference<K, V> {
        /**
         * Returns the value. Does not block or throw exceptions.
         */
        @Nullable
        V get();

        /**
         * Waits for a value that may still be loading. Unlike get(), this method can block (in the case of
         * FutureValueReference).
         * 
         * @throws ExecutionException if the loading thread throws an exception
         * @throws ExecutionError if the loading thread throws an error
         */
        V waitForValue() throws ExecutionException;

        /**
         * Returns the weight of this entry. This is assumed to be static between calls to setValue.
         */
        int getWeight();

        /**
         * Returns the entry associated with this value reference, or {@code null} if this value reference is
         * independent of any entry.
         */
        @Nullable
        ReferenceEntry<K, V> getEntry();

        /**
         * 為一個指定的entry創(chuàng)建一個該引用的副本
         * <p>
         * {@code value} may be null only for a loading reference.
         */
        ValueReference<K, V> copyFor(ReferenceQueue<V> queue, @Nullable V value, ReferenceEntry<K, V> entry);

        /**
         * 告知一個新的值正在加載中牧挣。這個只會關聯(lián)到加載值引用急前。
         */
        void notifyNewValue(@Nullable V newValue);

        /**
         * 當一個新的value正在被加載的時候,返回true瀑构。不管是否已經(jīng)有存在的值裆针。這里加鎖方法返回的值對于給定的ValueReference實例來說是常量。
         * 
         */
        boolean isLoading();

        /**
         * 返回true,如果該reference包含一個活躍的值,意味著在cache里仍然有一個值存在世吨≡枭玻活躍的值包含:cache查找返回的,等待被移除的要被驅趕的值耘婚; 非激活的包含:正在加載的值罢浇,
         */
        boolean isActive();
    }

介紹下ReferenceQueue引用隊列,這個隊列是JDK提供的沐祷,在檢測到適當?shù)目傻竭_性更改后嚷闭,垃圾回收器將已注冊的引用對象添加到該隊列中。因為Cache使用了各種引用戈轿,而通過ReferenceQueue這個“監(jiān)聽器”就可以優(yōu)雅的實現(xiàn)自動刪除那些引用不可達的key了

Segment 數(shù)據(jù)結構

Segment數(shù)據(jù)結構凌受,是ConcurrentHashMap的核心實現(xiàn),也是該結構保證了其算法的高效性思杯。在Guava Cache 中也一樣胜蛉,segment數(shù)據(jù)結構保證了緩存在線程安全的前提下可以高效地更新,插入色乾,獲取對應value誊册。

實際上,segment就是一個特殊版本的hash table實現(xiàn)暖璧。其內部也是對應一個鎖案怯,不同的是,對于get和put操作做了一些優(yōu)化處理澎办。因此嘲碱,在代碼實現(xiàn)的時候,為了快速開發(fā)和利用已有鎖特性局蚀,直接extends ReentrantLock.

在segment中麦锯,其主要的類屬性就是一個LoacalCache類型的map變量。關于segment實現(xiàn)說明琅绅,如下:

  /**
         * segments 維護一個entry列表的table扶欣,確保一致性狀態(tài)。所以可以不加鎖去讀千扶。節(jié)點的next field是不可修改的final料祠,因為所有l(wèi)ist的增加操作
         * 是執(zhí)行在每個容器的頭部。因此澎羞,這樣子很容易去檢查變化髓绽,也可以快速遍歷。此外妆绞,當節(jié)點被改變的時候顺呕,新的節(jié)點將被創(chuàng)建然后替換它們接谨。 由于容器的list一般都比較短(平均長度小于2),所以對于hash
         * tables來說塘匣,可以工作的很好。雖然說讀操作因此可以不需要鎖進行巷帝,但是是依賴
         * 使用volatile確保其他線程完成寫操作忌卤。對于絕大多數(shù)目的而言,count變量楞泼,跟蹤元素的數(shù)量驰徊,其作為一個volatile變量確保可見性(其內部原理可以參考其他相關博文)堕阔。
         * 這樣一下子變得方便的很多棍厂,因為這個變量在很多讀操作的時候都會被獲取:所有非同步的(unsynchronized)讀操作必須首先讀取這個count值超陆,并且如果count為0則不會 查找table
         * 的entries元素牺弹;所有的同步(synchronized)操作必須在結構性的改變任務bin容器之后,才會寫操作這個count值时呀。
         * 這些操作必須在并發(fā)讀操作看到不一致的數(shù)據(jù)的時候张漂,不采取任務動作。在map中讀操作性質可以更容易實現(xiàn)這個限制谨娜。例如:沒有操作可以顯示出 當table
         * 增長了航攒,但是threshold值沒有更新,所以考慮讀的時候不要求原子性趴梢。作為一個原則漠畜,所有危險的volatile讀和寫count變量都必須在代碼中標記。
         */

        final LocalCache<K, V> map;

        /**
         * 該segment區(qū)域內所有存活的元素個數(shù)
         */
        volatile int count;

        /**
         * 改變table大小size的更新次數(shù)坞靶。這個在批量讀取方法期間保證它們可以看到一致性的快照:
         * 如果modCount在我們遍歷段加載大小或者核對containsValue期間被改變了憔狞,然后我們會看到一個不一致的狀態(tài)視圖,以至于必須去重試滩愁。
         * count+modCount 保證內存一致性
         * 
         * 感覺這里有點像是版本控制躯喇,比如數(shù)據(jù)庫里的version字段來控制數(shù)據(jù)一致性
         */
        int modCount;

        /**
         * 每個段表,使用樂觀鎖的Array來保存entry The per-segment table.
         */
        volatile AtomicReferenceArray<ReferenceEntry<K, V>> table; // 這里和concurrentHashMap不一致硝枉,原因是這邊元素是引用廉丽,直接使用不會線程安全
        /**
         * A queue of elements currently in the map, ordered by write time. Elements are added to the tail of the queue
         * on write.
         */
        @GuardedBy("Segment.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("Segment.this")
        final Queue<ReferenceEntry<K, V>> accessQueue;

在segment實現(xiàn)中,很多地方使用count變量和modCount變量來保持線程安全妻味,從而省掉lock開銷正压。

LocalCache作為一個緩存,其必須具有訪問和寫超時特性责球,因為其內部維護了訪問隊列和寫隊列焦履,隊列中的元素按照訪問或者寫時間排序拓劝,新的元素會被添加到隊列尾部。如果嘉裤,在隊列中已經(jīng)存在了該元素郑临,則會先delete掉,然后再尾部add該節(jié)點屑宠,新的時間厢洞。這也就是為什么,對于LocalCache而言典奉,其LRU是針對segment的躺翻,而不是全Cache范圍的。

get put方法源碼分析請參照筆者的get put源碼分析

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末卫玖,一起剝皮案震驚了整個濱河市公你,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌假瞬,老刑警劉巖陕靠,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異脱茉,居然都是意外死亡懦傍,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進店門芦劣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來粗俱,“玉大人,你說我怎么就攤上這事虚吟〈缛希” “怎么了?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵串慰,是天一觀的道長偏塞。 經(jīng)常有香客問我,道長邦鲫,這世上最難降的妖魔是什么灸叼? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮庆捺,結果婚禮上古今,老公的妹妹穿的比我還像新娘。我一直安慰自己滔以,他們只是感情好捉腥,可當我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著你画,像睡著了一般抵碟。 火紅的嫁衣襯著肌膚如雪桃漾。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天拟逮,我揣著相機與錄音撬统,去河邊找鬼。 笑死敦迄,一個胖子當著我的面吹牛宪摧,可吹牛的內容都是我干的。 我是一名探鬼主播颅崩,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蕊苗!你這毒婦竟也來了沿后?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤朽砰,失蹤者是張志新(化名)和其女友劉穎尖滚,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體瞧柔,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡漆弄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了造锅。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片撼唾。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖哥蔚,靈堂內的尸體忽然破棺而出倒谷,到底是詐尸還是另有隱情,我是刑警寧澤糙箍,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布渤愁,位于F島的核電站,受9級特大地震影響深夯,放射性物質發(fā)生泄漏抖格。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一咕晋、第九天 我趴在偏房一處隱蔽的房頂上張望雹拄。 院中可真熱鬧,春花似錦掌呜、人聲如沸办桨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽呢撞。三九已至损姜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間殊霞,已是汗流浹背摧阅。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留绷蹲,地道東北人棒卷。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像祝钢,于是被迫代替她去往敵國和親比规。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,927評論 2 355