天天看點

Guava之Cache使用-Cache&LoadingCacheGuava之Cache使用-Cache&LoadingCache

Guava之Cache使用-Cache&LoadingCache

在使用本地緩存時,我們經常使用ConcurrentMap來實作,但是有時我們會存在一些需求,希望本地緩存資料能夠自動過期釋放等,在不引入第三方緩存插件(例如:ehcache)下,guava中的cache是一個不二之選

Cache&LoadingCahe

Cache:當不需要自動加載資料時,可直接使用Cache,傳回的是LocalCache中的靜态類LocalManualCache

LoadingCache:當需要自動加載資料時,可使用LoadingCache,傳回的是LocalCache中的靜态類LocalLoadingCache

Cache之CacheBuilder參數說明

Cache<String ,String> cache = CacheBuilder.newBuilder()
                // 并發級别,代表同時可以寫入緩存的線程數,用于建立segment,最大65535,預設4
                .concurrencyLevel(6)
                // 當緩存項在指定時間範圍内沒有被讀或寫就失效
                //.expireAfterAccess()
                // 當緩存項在指定時間範圍内沒有被更新就失效,load新值過程中,阻塞查詢
                //.expireAfterWrite()
                // 當緩存項在上一次更新後,多久失效,當load新值過程中,查詢傳回舊值,可能傳回的是很久之前的就值,因為資料更新通過查詢觸發
                // requires a LoadingCache
                //.refreshAfterWrite()
                // 初始大小,預設16
                .initialCapacity(10)
                // 設定緩存最大容量,逼近時按照LRU移除
                .maximumSize(20)
                // 開啟統計記錄
                .recordStats()
                // 設定緩存最大權重,逼近時按照LRU移除
                //.maximumWeight(100)
                // 使用軟引用存儲value。軟引用就是在記憶體不夠是才會按照順序回收。
                //.softValues()
                // 使用弱引用存儲key,當被垃圾回收的時候,目前key沒有其他引用的時候緩存項可以被垃圾回收。
                //.weakKeys()
                // 使用弱引用存儲value,當被垃圾回收的時候,目前value沒有其他引用的時候緩存項可以被垃圾回收。
                //.weakValues()
                // 使用自定義Ticker靈活控制時間,模拟時間流逝
                //.ticker(Ticker.systemTicker())
                .build();
           

使用示例

基于容量大小回收maximumSize

當容量逼近最大值時,就開始進行回收,并不是到達最大值時才開始回收,回收政策LRU

public static void test0(){
        Cache<String ,String> cache = CacheBuilder.newBuilder()
                .maximumSize(20)
                .recordStats()
                .build();
        for (int i = 0; i < 20; i++){
            cache.put("key"+i,"value"+i);
        }
        System.out.println(cache.stats().toString());
        System.out.println(cache.size());
        outCache(cache.asMap());
    }
    public static void outCache(Map<String, String>  map){
        for(Map.Entry<String, String> entry: map.entrySet()) {
            System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
        }
    }
           

執行結果:

CacheStats{hitCount=0, missCount=0, loadSuccessCount=0, loadExceptionCount=0, totalLoadTime=0, evictionCount=3}
17
Key = key12, Value = value12
Key = key6, Value = value6
Key = key18, Value = value18
Key = key15, Value = value15
Key = key16, Value = value16
Key = key10, Value = value10
Key = key9, Value = value9
Key = key8, Value = value8
Key = key7, Value = value7
Key = key17, Value = value17
Key = key19, Value = value19
Key = key13, Value = value13
Key = key0, Value = value0
Key = key3, Value = value3
Key = key11, Value = value11
Key = key14, Value = value14
Key = key5, Value = value5
           

evictionCount=3 結果中,從第18個開始就進行了緩存驅逐,這是因為内部segment中存在this.totalWeight <= this.maxSegmentWeight判斷,該細節會在後面介紹,也可仔細閱讀源碼了解

緩存記錄統計-CacheStats

public static void test1(){
        Cache<String ,String> cache = CacheBuilder.newBuilder()
                .maximumSize(20)
                .recordStats()
                .build();
        for (int i = 0; i < 10; i++){
            cache.put("key"+i,"value"+i);
        }
        //命中
        cache.getIfPresent("key1");
        cache.getIfPresent("key2");
        //未命中
        cache.getIfPresent("key20");
        System.out.println(cache.stats().toString());
        System.out.println(cache.size());
        outCache(cache.asMap());
    }
           

執行結果:

CacheStats{hitCount=2, missCount=1, loadSuccessCount=0, loadExceptionCount=0, totalLoadTime=0, evictionCount=0}
10
Key = key6, Value = value6
Key = key1, Value = value1
Key = key4, Value = value4
Key = key2, Value = value2
Key = key9, Value = value9
Key = key8, Value = value8
Key = key7, Value = value7
Key = key3, Value = value3
Key = key5, Value = value5
Key = key0, Value = value0
           

hitCount:緩存命中 missCount:緩存丢失命中

基于引用的回收

基于引用回收包括:軟引用softValues()、弱引用weakKeys()和weakValues()

以weakValues()為例:

public static void test2(){
        Cache<String ,String> cache = CacheBuilder.newBuilder()
                .weakValues()
                .recordStats()
                .build();
        for (int i = 0; i < 20; i++){
            cache.put("key"+i,"value"+i);
        }
        String value = cache.getIfPresent("key1");
        System.gc();
        System.out.println(cache.stats().toString());
        System.out.println(cache.size());
        outCache(cache.asMap());
    }
           

執行結果:

CacheStats{hitCount=1, missCount=0, loadSuccessCount=0, loadExceptionCount=0, totalLoadTime=0, evictionCount=0}
20
Key = key1, Value = value1
           

在系統GC時,沒有被外部引用的緩存項會被系統回收

但是cache.size()仍然還是不變(20)

定時回收

定時回收包括:expireAfterAccess、expireAfterWrite、refreshAfterWrite

expireAfterAccess:時間範圍内,沒有進行讀或寫,則回收

expireAfterWrite:時間範圍内,沒有進行更新,則回收

refreshAfterWrite:超出時間後過期,隻能在LoadingCache中使用

以expireAfterWrite為例:

private static class DemoTicker extends Ticker {
        private long start = Ticker.systemTicker().read();
        private long offsetTime = 0;

        @Override
        public long read() {
            return start + offsetTime;
        }

        public void addTime(long offsetTime) {
            this.offsetTime = offsetTime;
        }
    }
    public static void test3(){
        //模拟時間流逝
        DemoTicker demoTicker = new DemoTicker();
        Cache<String ,String> cache = CacheBuilder.newBuilder()
                .initialCapacity(10)
                //.expireAfterAccess(10, TimeUnit.MINUTES)
                .expireAfterWrite(10, TimeUnit.MINUTES)
                //.refreshAfterWrite(10, TimeUnit.MINUTES)
                .recordStats()
                .ticker(demoTicker)
                .build();
        for (int i = 0; i < 20; i++){
            cache.put("key"+i,"value"+i);
        }
        demoTicker.addTime(TimeUnit.NANOSECONDS.convert(5,TimeUnit.MINUTES));
        //cache.getIfPresent("key1");
        cache.put("key1","hehe");
        demoTicker.addTime(TimeUnit.NANOSECONDS.convert(10,TimeUnit.MINUTES));
        System.out.println(cache.stats().toString());
        System.out.println(cache.size());
        outCache(cache.asMap());
    }
           

執行結果:

CacheStats{hitCount=0, missCount=0, loadSuccessCount=0, loadExceptionCount=0, totalLoadTime=0, evictionCount=0}
20
Key = key1, Value = hehe
           

時間通路内未被使用到的緩存項,自動被回收,但是cache.size()仍然還是不變(20)

自動加載

自動加載包括:統一規則load,自定義規則load

統一規則:使用LoadingCache CacheLoader實作

自定義規則:使用Callable callback

public static void test1() throws ExecutionException {
        //模拟時間流逝
        DemoTicker demoTicker = new DemoTicker();
        LoadingCache<String,String> cache = CacheBuilder.newBuilder()
                .maximumSize(10)
                .recordStats()
                .refreshAfterWrite(5, TimeUnit.SECONDS)
                .ticker(demoTicker)
                .build(new CacheLoader<String, String>() {
                    @Override
                    public String load(String s) throws Exception {
                        return RandomStringUtils.randomAlphanumeric(10);
                    }
                });
        for (int i = 0; i < 10; i++){
            cache.put("key"+i,"value"+i);
        }
        demoTicker.addTime(TimeUnit.NANOSECONDS.convert(6,TimeUnit.SECONDS));
        //get觸發更新
        cache.getIfPresent("key1"); //使用統一load
        //使用自定義單獨load
        cache.get("key2", new Callable<String>() {
            public String call() throws Exception {
                return "222";
            }
        });
        System.out.println(cache.size());
        System.out.println(cache.stats().toString());
        CacheUtil.outCache(cache.asMap());
    }
           

執行結果:

10
CacheStats{hitCount=2, missCount=0, loadSuccessCount=2, loadExceptionCount=0, totalLoadTime=32281287, evictionCount=0}
Key = key6, Value = value6
Key = key1, Value = SUNZEIaUH0
Key = key4, Value = value4
Key = key2, Value = 222
Key = key0, Value = value0
Key = key9, Value = value9
Key = key3, Value = value3
Key = key8, Value = value8
Key = key7, Value = value7
Key = key5, Value = value5
           

元素移除/替換監視

監視事件:

1、當元素由于size或weight逼近上限時發生移除

2、當元素過期重新load替換舊資料

public static void test2() throws ExecutionException {
        //模拟時間流逝
        DemoTicker demoTicker = new DemoTicker();
        LoadingCache<String,String> cache = CacheBuilder.newBuilder()
                .maximumSize(10)
                .refreshAfterWrite(5, TimeUnit.SECONDS)
                .ticker(demoTicker)
                .recordStats()
                .removalListener(new RemovalListener<String, String>() {
                    public void onRemoval(RemovalNotification<String, String> removalNotification) {
                        System.out.println(String.format("key=%s,value=%s,reason=%s", removalNotification.getKey(), removalNotification.getValue(), removalNotification.getCause()));
                    }
                })
                .build(new CacheLoader<String, String>() {
                    @Override
                    public String load(String s) throws Exception {
                        return RandomStringUtils.randomAlphanumeric(10);
                    }
                });
        for (int i = 0; i < 15; i++){
            cache.put("key"+i,"value"+i);
        }
        demoTicker.addTime(TimeUnit.NANOSECONDS.convert(6,TimeUnit.SECONDS));
        //get觸發更新
        cache.getIfPresent("key13");
        cache.get("key14", new Callable<String>() {
            public String call() throws Exception {
                return "222";
            }
        });
        System.out.println(cache.size());
        CacheUtil.outCache(cache.asMap());
    }
           

執行結果:

key=key0,value=value0,reason=SIZE
key=key1,value=value1,reason=SIZE
key=key2,value=value2,reason=SIZE
key=key3,value=value3,reason=SIZE
key=key4,value=value4,reason=SIZE
key=key13,value=value13,reason=REPLACED
key=key14,value=value14,reason=REPLACED
10
Key = key12, Value = value12
Key = key6, Value = value6
Key = key13, Value = NU6vlPNRiM
Key = key10, Value = value10
Key = key9, Value = value9
Key = key8, Value = value8
Key = key7, Value = value7
Key = key11, Value = value11
Key = key14, Value = 222
Key = key5, Value = value5