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 extends AbstractMap implements ConcurrentMap {
  ...
}
  • demo

pom文件

        
            com.google.guava
            guava
            23.0
        

java代码


    public static void main(String[] args) throws Exception {
        LoadingCache cache = CacheBuilder.newBuilder()
                .build(new CacheLoader() {
                    @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(){...})即可创建一个LocalCache,build里面的new CacheLoader(){...}是根据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 cache = CacheBuilder.newBuilder()
                .build(new CacheLoader() {
                    @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)。

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

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

        LoadingCache cache = CacheBuilder.newBuilder()
                .build(new CacheLoader() {
                    @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() {
            @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 cache = CacheBuilder.newBuilder()
                .maximumSize(3)
                .build(new CacheLoader() {
                    @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 cache = CacheBuilder.newBuilder()
                .expireAfterAccess(4, TimeUnit.SECONDS)
                .build(new CacheLoader() {
                    @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 cache = CacheBuilder.newBuilder()
                .expireAfterWrite(4, TimeUnit.SECONDS)
                .build(new CacheLoader() {
                    @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 cache = CacheBuilder.newBuilder()
                .weakKeys()
                .build(new CacheLoader() {
                    @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 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 cache = CacheBuilder.newBuilder()
                .removalListener(new RemovalListener() {
                    @Override
                    public void onRemoval(RemovalNotification removalNotification) {
                        System.out.println("remove key = " + removalNotification.getKey() + ", value = " + removalNotification.getValue());
                    }
                })
                .build(new CacheLoader() {
                    @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。

你可能感兴趣的:(guava cache学习)