Guava之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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值