guava cache学习

  • guava cache简介

为什么会有guava cache

实际开发中,有时候会有一些不常修改,但是经常会被用到的数据,它们可能放在数据库里,也可能放在配置文件等。每次用到它们,如果都去查数据库,读取配置文件的话,那么效率是比较慢的。我们可以把数据集中放到一个地方,每次查询的时候就去那个地方查。这个地方可以是redis,相比查数据库,redis效率显然会更快,但是还是得跨网络。这个地方也可以是JVM内存,在所有的存放数据的地方中,内存无疑是最快的。存放数据,又是jvm内存,你可能会想到HashMap,如果是比较简单的需求,HashMap已经满足我们的需求了;但是如果你存放数据的同时,又要求可能像redis一样可以设置过期时间,还要求一定要线程安全,这时候HashMap可能就有点力不从心了。而guava cache就是这样一款可以把数据存放到JVM内存,还具有设置过期时间等功能的缓存工具。

guava cache

你可以把 guava cache当做一个可以设定过期机制,多线程安全的map;实际上,guava cache底层的LocalCache也确实是继承了ConcurrentMap。

class LocalCache<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V> {
  ...
}
  • demo

pom文件

        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>23.0</version>
        </dependency>

java代码


    public static void main(String[] args) throws Exception {
        LoadingCache<String, Object> cache = CacheBuilder.newBuilder()
                .build(new CacheLoader<String, Object>() {
                    @Override
                    public Object load(String key) throws Exception {
                        return getValue(key);
                    }
                });
        System.out.println("key.length = " + cache.get("hello"));
    }

    private static Object getValue(String key) {
        return key.length();
    }

利用CacheBuilder.newBuilder().build(new CacheLoader<String, Object>(){...})即可创建一个LocalCache,build里面的new CacheLoader<String, Object>(){...}是根据key加载获取value的方式,我们可以调取rmi加载获取相关信息,也可以从数据库加载获取信息,还可以从配置文件加载获取信息,获取到value以后,LocalCache会把数据保存到内存中,如果第二次获取该key的数据的话,直接从内存中读取数据返回,而不用再次加载了。我这里做演示用,所以只是保存了key的长度。

  • guava cache配置

加载与插入

guava cache和hashMap最大的不同就是,hashMap想要get某个key对应的value的时候,要先显式把value put进去,但是guava cache有一个加载机制,在创建LocalCache或者get 的时候传入CacheLoader或者Callable实例,每次我们get,guava cache会首先从cache里面查找是否有key对应的value,如果有,返回,如果没有,会调用LocalCache或者callable加载key对应的value,然后隐式的put到cache中并且返回,从而我们不用先显式把value put 进去。

  1. CacheLoader
    public static void main(String[] args) throws Exception {
        LoadingCache<String, Object> cache = CacheBuilder.newBuilder()
                .build(new CacheLoader<String, Object>() {
                    @Override
                    public Object load(String key) throws Exception {
                        return getValue(key);
                    }
                });

一般我们可以通过cache.get(key)来获取key对应的value,但是这个办法会抛出ExecutionException 的异常,如果我们不想每次都写个try catch来处理ExecutionException ,我们可以用cache.getUnchecked(key),如果用getUnchecked,CacheLoader加载器中不要声明任何检查型异常;批量获取可以用getAll(Iterable<? extends K>)。

  1. Callable
        Cache<String, Object> cache = CacheBuilder.newBuilder().build();
        cache.put("hello", "hi");
        System.out.println(cache.get("hello", new Callable<Object>() {
            @Override
            public Object call() throws Exception {
                return "hello";
            }
        }));

我的理解是,这种加载办法是对CacheLoader,或者put的一种补充,如果get找不到对应的value(即返回null),就会调用Callable的回调方法call。比如:

        LoadingCache<String, Integer> cache = CacheBuilder.newBuilder()
                .build(new CacheLoader<String, Integer>() {
                    @Override
                    public Integer load(String key) throws Exception {
                        return 2;
                    }
                });
      //如果上面的load return 2,这里的sout会输出2, 如果上面的load return null,这里的sout会输出1。
        System.out.println(cache.get("hello", new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                return 1;
            }
        }));
  1. put
    guava cache也可以像我们普通的map那样显式把key,value put到cache中。
回收

guava cache 和 hashMap第二个不同就是,guava cache有很多种回收机制可以隐式移除某些key,而在hashMap中,我们只能显式的移除key(map.remove(key))。

  1. 基于容量的回收
    CacheBuilder.maximumSize(long)
    设置cache的大小,当cache存放的元素超过最大值的时候,最先放入cache的元素会被剔除掉。
        LoadingCache<String, Integer> cache = CacheBuilder.newBuilder()
                .maximumSize(3)
                .build(new CacheLoader<String, Integer>() {
                    @Override
                    public Integer load(String key) throws Exception {
                        System.out.println("--load key =" + key +" --");
                        return key.length();
                    }
                });
        System.out.println(cache.get("a"));
        System.out.println(cache.get("a"));
        System.out.println(cache.get("b"));
        System.out.println(cache.get("c"));
        System.out.println(cache.get("d"));
        System.out.println(cache.get("a"));
      //我们设置了cache最多能存放3个元素,a是最先放进来的,当放完b,c,d以后,再次取a的时候还是需要加载。   
  1. 定时回收
  • expireAfterAccess(long, TimeUnit):缓存项在给定时间内没有被读/写访问,则回收。请注意这种缓存的回收顺序和基于大小回收一样。
        LoadingCache<String, Integer> cache = CacheBuilder.newBuilder()
                .expireAfterAccess(4, TimeUnit.SECONDS)
                .build(new CacheLoader<String, Integer>() {
                    @Override
                    public Integer load(String key) throws Exception {
                        System.out.println("--load key =" + key +" --");
                        return key.length();
                    }
                });
        System.out.println(cache.get("a"));
        Thread.sleep(3000);
        System.out.println(cache.get("a"));
        Thread.sleep(3000);
        System.out.println(cache.get("a"));
        Thread.sleep(4100);
        System.out.println(cache.get("a"));
      //第一次get会load,第二次,第三次都不会load,第四次因为过了4秒了,所以会load。
  • expireAfterWrite(long, TimeUnit):缓存项在给定时间内没有被写访问(创建或覆盖),则回收。如果认为缓存数据总是在固定时候后变得陈旧不可用,这种回收方式是可取的。
        LoadingCache<String, Integer> cache = CacheBuilder.newBuilder()
                .expireAfterWrite(4, TimeUnit.SECONDS)
                .build(new CacheLoader<String, Integer>() {
                    @Override
                    public Integer load(String key) throws Exception {
                        System.out.println("--load key =" + key +" --");
                        return key.length();
                    }
                });
        System.out.println(cache.get("a"));
        Thread.sleep(3000);
        System.out.println(cache.get("a"));
        Thread.sleep(3000);
        System.out.println(cache.get("a"));
        Thread.sleep(4100);
        System.out.println(cache.get("a"));
      //第一次get会load,第二次因为还没到4s,所以不会load,第三次会load,因为距离第一次get已经有6秒,超过4秒了,第四次也会load,因为距离第二次get超过了4s。
  1. 基于引用的回收
    通过使用弱引用的键、或弱引用的值、或软引用的值,Guava Cache可以把缓存设置为允许垃圾回收:
  • CacheBuilder.weakKeys():使用弱引用存储键。当键没有其它(强或软)引用时,缓存项可以被垃圾回收。因为垃圾回收仅依赖恒等式(==),使用弱引用键的缓存用==而不是equals比较键。
        LoadingCache<Cat, Integer> cache = CacheBuilder.newBuilder()
                .weakKeys()
                .build(new CacheLoader<Cat, Integer>() {
                    @Override
                    public Integer load(Cat cat) throws Exception {
                        System.out.println("--load key =" + cat.getName() + " --");
                        return cat.getAge();
                    }
                });
       
      // 到最后我们会发现,当cache.size达到4个以后就不会再增加了,这是因为new Cat("cat:" + i, i + 10000)没有被任何东西引用到,每次gc就会被回收掉,如果被gc掉的话,则cache中的key也会没了,所以cache.size才不会源源不断的增长。至于为什么还有4只,我也不太清楚
         int i = 1;
        while (true) {
            cache.get(new Cat("cat:" + i, i + 10000));
            System.out.println(cache.size());
            System.gc();
            Thread.sleep(1000);
        }
        //如果我们用这段代码,会发现即使有gc,cache.size还是会源源不断的增长,这是因为cat会放到了list中,有被引用到。
        int i = 1;
        List<Cat> cats = new ArrayList<>();
        while (true) {
            Cat c = new Cat("cat:" + i, i + 10000);
            cats.add(c);
            cache.get(c);
            System.out.println(cache.size());
            System.gc();
            Thread.sleep(1000);
        }
  • CacheBuilder.weakValues():使用弱引用存储值。当值没有其它(强或软)引用时,缓存项可以被垃圾回收。因为垃圾回收仅依赖恒等式(==),使用弱引用值的缓存用==而不是equals比较值。
  • CacheBuilder.softValues():使用软引用存储值。软引用只有在响应内存需要时,才按照全局最近最少使用的顺序回收。考虑到使用软引用的性能影响,我们通常建议使用更有性能预测性的缓存大小限定(见上文,基于容量回收)。使用软引用值的缓存同样用==而不是equals比较值。
  1. 显式清除
  • 个别清除:Cache.invalidate(key)
  • 批量清除:Cache.invalidateAll(keys)
  • 清除所有缓存项:Cache.invalidateAll()
  1. 移除监听器
    我们可以设置移除监听器,当key被移除的时候(invalidate)会触发监听器。
  LoadingCache<String, Integer> cache = CacheBuilder.newBuilder()
                .removalListener(new RemovalListener<String, Integer>() {
                    @Override
                    public void onRemoval(RemovalNotification<String, Integer> removalNotification) {
                        System.out.println("remove key = " + removalNotification.getKey() + ", value = " + removalNotification.getValue());
                    }
                })
                .build(new CacheLoader<String, Integer>() {
                    @Override
                    public Integer load(String key) throws Exception {
                        System.out.println("--load key =" + key + " --");
                        return key.length();
                    }
                });
        cache.get("hello");
        cache.invalidate("hello");
  1. 何时移除
    guava cache从来不会主动会回收不符合我们设置回收机制的key-value,即不会开一个线程去回收,比如我们设置了expireAfterAccess(5s),某个键值达到了5s以后,并不会触动监听器,只有当5s以后,我们再次调用该key的时候才会触发该监听器,就算是调用其他key也不会回收该key。
统计
  1. recordStats() 开启统计
  2. asMap() 获取key value的map
  • 原理

LocalCache的数据结构与ConcurrentHashMap很相似,都由多个segment组成,且各个segment相对独立,互不影响,所以能支持并行操作。每个segment由一个table和若干队列组成。缓存数据存储在table中,其类型为AtomicReferenceArray。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容

  • Google Guava Cache是一种非常优秀本地缓存解决方案,提供了基于容量,时间和引用的缓存回收方式。基于...
    Acamy丶阅读 25,746评论 3 34
  • com.google.common.cache 1、背景 缓存,在我们日常开发中是必不可少的一种解决性能问题的方法...
    拾壹北阅读 22,210评论 0 25
  • 世上有 已知的有趣灵魂 世上还有 未知的有趣灵魂 我们需要新的朋友 和他们的有趣灵魂 我们需要朋友 和他们有趣的灵魂
    伍月的四月阅读 160评论 2 3
  • 提需求: 态度上:自己先把各个板块搞清楚,不要模棱两可,不确定,期待别人来拍板,要或者不要,都有理由 经验上:跳转...
    默沉陌尘阅读 163评论 0 0
  • 今天是双11,似乎从2013年起,双11这个光棍节就变成了购物狂欢节。很少就再有人叫嚣着双十一不脱单怎样怎样,很少...
    十月公子阅读 120评论 0 0