一、概述

  Guava Cache与ConcurrentMap很相似,但也不完全一样。最基本的区别是ConcurrentMap会一直保存所有添加的元素,直到显式地移除。相对地,Guava Cache为了限制内存占用,通常都设定为自动回收元素。在某些场景下,尽管LoadingCache 不回收元素,它也是很有用的,因为它会自动加载缓存。

1.1、使用场景

通常来说,Guava Cache适用于:

  • 你愿意消耗一些内存空间来提升速度。
  • 你预料到某些键会被查询一次以上。
  • 缓存中存放的数据总量不会超出内存容量。(Guava Cache是单个应用运行时的本地缓存。它不把数据存放到文件或外部服务器。如果这不符合你的需求,请尝试redis这类工具)

如果你的场景符合上述的每一条,Guava Cache就适合你。

:如果你不需要Cache中的特性,使用ConcurrentHashMap有更好的内存效率——但Cache的大多数特性都很难基于旧有的ConcurrentMap复制,甚至根本不可能做到。

二、使用

2.1、Guava Cache有以下两种创建方式:

  通过这两种方法创建的cache,和通常用map来缓存的做法比,不同在于,这两种方法都实现了一种逻辑——从缓存中取key X的值,如果该值已经缓存过了,则返回缓存中的值,如果没有缓存过,可以通过某个方法来获取这个值。但不同的在于cacheloader的定义比较宽泛,是针对整个cache定义的,可以认为是统一的根据key值load value的方法。而callable的方式较为灵活,允许你在get的时候指定。

方式一、创建 CacheLoader

  LoadingCache是附带CacheLoader构建而成的缓存实现。创建自己的CacheLoader通常只需要简单地实现V load(K key) throws Exception方法。例如,你可以用下面的代码构建LoadingCache:
  CacheLoader: 当检索不存在的时候,会自动的加载信息的

    class Person{
private String name; public Person(String name) {
this.name = name;
} @Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
public com.google.common.cache.CacheLoader<String, Person> createCacheLoader() {
return new com.google.common.cache.CacheLoader<String, Person>() {
@Override
public Person load(String key) throws Exception {
System.out.println("加载创建key:" + key);
return new Person(key+":ddd");
}
};
} @Test
public void testCreateCacheLoader() throws ExecutionException {
LoadingCache<String, Person> cache = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(createCacheLoader());
cache.put("aa",new Person("aaa"));
Person aa = cache.get("aa");
System.out.println(aa);//Person{name='aaa'} Person bb = cache.get("bb");
System.out.println(bb); //加载创建key:bb Person{name='bb:ddd'}
}

方式二、创建 Callable

    @Test
public void testCreateCallable() throws Exception {
Cache<String, Person> cache = CacheBuilder.newBuilder()
.maximumSize(1000)
.build(); // look Ma, no CacheLoader try {
cache.put("aa", new Person("aaaa"));
// If the key wasn't in the "easy to compute" group, we need to
// do things the hard way.
Person aa = cache.get("aa", new Callable<Person>() {
@Override
public Person call() throws Exception {
return new Person("defalut");
// return doThingsTheHardWay(key);
}
});
System.out.println(aa);//Person{name='aaaa'}
} catch (Exception e) {
throw new Exception(e.getCause());
} Person bb = cache.get("bb", () -> new Person("defalut"));
System.out.println(bb); //Person{name='defalut'}
}

cache的参数

基本方法介绍

1、getIfPresent(Object key);  该方法从本地缓存中找值,如果找不到返回null,找到就返回相应的值。

2、get:首先会在缓存中找,缓存中找不到再通过load加载。

3、remove(@Nullable Object key);调用LocalManualCache的invalidate(Object key)方法即可调用remove.

4、evictEntries(ReferenceEntry<K, V> newest);传入的参数为最新的Entry,可能是刚插入的,也可能是刚更新过的。

该方法只有在设置了在构建缓存的时候指定了maximumSize才会往下执行。首先清除recencyQueue,判断该元素自身的权重是否超过上限,如果超过则移除当前元素。然后判断总的权重是否大于上限,如果超过则去accessQueue里找到队首(即最不常访问的元素)进行

5、preWriteCleanup(long now);传人参数只有当前时间。键值引用队列中都是存储已经被GC,等待清除的entry信息,所以首先去处理这个里面的entry.

读写队列里面是按照读写时间排序的,取出队列中的首元素,如果当前时间与该元素的时间相差值大于设定值,则进行回收。

6、put

public V put(K key, V value); //onlyIfAbsent为false

public V putIfAbsent(K key, V value); //onlyIfAbsent为true

该方法显式往本地缓存里面插入值。从下面的流程图中可以看出,在执行每次put前都会进行preWriteCleanUP,在put返回前如果更新了entry则要进行evictEntries操作。

7、getUnchecked

  如果你的CacheLoader没有定义任何checked Exception,那你可以使用getUnchecked。

2.2、显示插入数据

  使用cache.put(key, value)方法可以直接向缓存中插入值,这会直接覆盖掉给定键之前映射的值。使用Cache.asMap()视图提供的任何方法也能修改缓存。但请注意,asMap视图的任何方法都不能保证缓存项被原子地加载到缓存中

  进一步说,asMap视图的原子运算在Guava Cache的原子加载范畴之外,所以相比于Cache.asMap().putIfAbsent(K,V),Cache.get(K, Callable<V>) 应该总是优先使用。

2.3、缓存回收

  Guava Cache提供了三种基本的缓存回收方式:基于容量回收、定时回收和基于引用回收。

1、基于容量的回收(size-based eviction)

  大小

  如果要规定缓存项的数目不超过固定值,只需使用CacheBuilder.maximumSize(long)。缓存将尝试回收最近没有使用或总体上很少使用的缓存项。  

  警告:在缓存项的数目达到限定值之前,缓存就可能进行回收操作——通常来说,这种情况发生在缓存项的数目逼近限定值时。

  权重

  另外,不同的缓存项有不同的“权重”(weights)——例如,如果你的缓存值,占据完全不同的内存空间,你可以使用CacheBuilder.weigher(Weigher)指定一个权重函数,并且用CacheBuilder.maximumWeight(long)指定最大总重。在权重限定场景中,除了要注意回收也是在重量逼近限定值时就进行了,还要知道重量是在缓存创建时计算的,因此要考虑重量计算的复杂度。

    @Test
public void testWeight() throws Exception {
LoadingCache<String, Person> cache = CacheBuilder.newBuilder()
.maximumWeight(5)
.weigher((Weigher<String, Person>) (s, person) -> {
//权重计算器
int weight = person.name.length();
System.out.println("key:"+s);
return weight;
})
.build(new CacheLoader<String, Person>() {
@Override
public Person load(String key) {
System.out.println("加载创建key:" + key);
return new Person(key + ":default");
}
}); cache.put("a",new Person("aaaaaaa1"));
cache.put("b",new Person("bbbbbb1"));
cache.put("c",new Person("cc1")); Person a = cache.get("a");
System.out.println(a);
Person b = cache.get("b");
System.out.println(b);
Person c = cache.get("c");
System.out.println(c); //缓存只有 一个 c
System.out.println(cache.asMap());
}

输出:

key:a
key:b
key:c
加载创建key:a
key:a
Person{name='a:default'}
加载创建key:b
key:b
Person{name='b:default'}
Person{name='cc1'}
{c=Person{name='cc1'}}

2、定时回收(Timed Eviction)

CacheBuilder提供两种定时回收的方法:

  • expireAfterAccess(long, TimeUnit):缓存项在给定时间内没有被读/写访问,则回收。请注意这种缓存的回收顺序和基于大小回收一样。【读一次多久后没有被访问过期】
  • expireAfterWrite(long, TimeUnit):缓存项在给定时间内没有被写访问(创建或覆盖),则回收。如果认为缓存数据总是在固定时候后变得陈旧不可用,这种回收方式是可取的。【写完多久后过期】

如下文所讨论,定时回收周期性地在写操作中执行,偶尔在读操作中执行。

    @Test
public void testEvictionByAccessTime() throws ExecutionException, InterruptedException {
LoadingCache<String, Person> cache = CacheBuilder.newBuilder()
.expireAfterAccess(2, TimeUnit.SECONDS)
.build(createCacheLoader());
cache.getUnchecked("wangji");
TimeUnit.SECONDS.sleep(3);
Person employee = cache.getIfPresent("wangji"); //不会重新加载创建cache
System.out.println("被销毁:" + (employee == null ? "是的" : "否"));
cache.getUnchecked("guava"); TimeUnit.SECONDS.sleep(1);
employee = cache.getIfPresent("guava"); //会重新加载创建cache
System.out.println("被销毁:" + (employee == null ? "是的" : "否")); TimeUnit.SECONDS.sleep(2);
employee = cache.getIfPresent("guava"); //不会重新加载创建cache
System.out.println("被销毁:" + (employee == null ? "是的" : "否")); TimeUnit.SECONDS.sleep(2);
employee = cache.getIfPresent("guava"); //不会重新加载创建cache
System.out.println("被销毁:" + (employee == null ? "是的" : "否")); TimeUnit.SECONDS.sleep(2);
employee = cache.getIfPresent("guava"); //不会重新加载创建cache
System.out.println("被销毁:" + (employee == null ? "是的" : "否"));
}

输出

加载创建key:wangji
被销毁:是的
加载创建key:guava
被销毁:否
被销毁:是的
被销毁:是的
被销毁:是的

3、基于引用的回收(Reference-based Eviction)【强(strong)、软(soft)、弱(weak)、虚(phantom】

通过使用弱引用的键、或弱引用的值、或软引用的值,Guava Cache可以把缓存设置为允许垃圾回收:

  • CacheBuilder.weakKeys():使用弱引用存储键。当键没有其它(强或软)引用时,缓存项可以被垃圾回收。因为垃圾回收仅依赖恒等式(==),使用弱引用键的缓存用==而不是equals比较键。
  • CacheBuilder.weakValues():使用弱引用存储值。当值没有其它(强或软)引用时,缓存项可以被垃圾回收。因为垃圾回收仅依赖恒等式(==),使用弱引用值的缓存用==而不是equals比较值。
  • CacheBuilder.softValues():使用软引用存储值。软引用只有在响应内存需要时,才按照全局最近最少使用的顺序回收。考虑到使用软引用的性能影响,我们通常建议使用更有性能预测性的缓存大小限定(见上文,基于容量回收)。使用软引用值的缓存同样用==而不是equals比较值。
    @Test
public void testWeakKey() throws ExecutionException, InterruptedException {
LoadingCache<String, Person> cache = CacheBuilder.newBuilder()
// .weakValues()
.weakKeys()
.softValues()
.build(createCacheLoader());
cache.getUnchecked("guava");
cache.getUnchecked("wangji"); System.gc();
TimeUnit.MILLISECONDS.sleep(100);
Person employee = cache.getIfPresent("guava"); //不会重新加载创建cache
System.out.println("被销毁:" + (employee == null ? "是的" : "否"));
}

输出:

加载创建key:guava
加载创建key:wangji
被销毁:否

2.4、显式清除

  任何时候,你都可以显式地清除缓存项,而不是等到它被回收:
  个别清除:Cache.invalidate(key)
  批量清除:Cache.invalidateAll(keys)
  清除所有缓存项:Cache.invalidateAll()

清理什么时候发生

  使用CacheBuilder构建的缓存不会”自动”执行清理和回收工作,也不会在某个缓存项过期后马上清理,也没有诸如此类的清理机制。相反,它会在写操作时顺带做少量的维护工作,或者偶尔在读操作时做——如果写操作实在太少的话。

  这样做的原因在于:如果要自动地持续清理缓存,就必须有一个线程,这个线程会和用户操作竞争共享锁。此外,某些环境下线程创建可能受限制,这样CacheBuilder就不可用了。

  相反,我们把选择权交到你手里。如果你的缓存是高吞吐的,那就无需担心缓存的维护和清理等工作。如果你的 缓存只会偶尔有写操作,而你又不想清理工作阻碍了读操作,那么可以创建自己的维护线程,以固定的时间间隔调用Cache.cleanUp()。

  ScheduledExecutorService可以帮助你很好地实现这样的定时调度。

刷新

  刷新和回收不太一样。正如LoadingCache.refresh(K)所声明,刷新表示为键加载新值,这个过程可以是异步的。在刷新操作进行时,缓存仍然可以向其他线程返回旧值,而不像回收操作,读缓存的线程必须等待新值加载完成。

    @Test
public void testCacheRefresh() throws InterruptedException {
AtomicInteger counter = new AtomicInteger(0);
CacheLoader<String, Long> cacheLoader = CacheLoader.from(k -> {
counter.incrementAndGet();
System.out.println("创建 key :" + k);
return System.currentTimeMillis();
});
LoadingCache<String, Long> cache = CacheBuilder.newBuilder()
.refreshAfterWrite(2, TimeUnit.SECONDS) // 2s后重新刷新
.build(cacheLoader); Long result1 = cache.getUnchecked("guava");
TimeUnit.SECONDS.sleep(3);
Long result2 = cache.getUnchecked("guava");
System.out.println(result1.longValue() != result2.longValue() ? "是的" : "否");
}

  CacheBuilder.refreshAfterWrite(long, TimeUnit)可以为缓存增加自动定时刷新功能。和expireAfterWrite相反,refreshAfterWrite通过定时刷新可以让缓存项保持可用,但请注意:缓存项只有在被检索时才会真正刷新(如果CacheLoader.refresh实现为异步,那么检索不会被刷新拖慢)。因此,如果你在缓存上同时声明expireAfterWrite和refreshAfterWrite,缓存并不会因为刷新盲目地定时重置,如果缓存项没有被检索,那刷新就不会真的发生,缓存项在过期时间后也变得可以回收。

2.5、移除监听器

  通过CacheBuilder.removalListener(RemovalListener),你可以声明一个监听器,以便缓存项被移除时做一些额外操作。缓存项被移除时,RemovalListener会获取移除通知[RemovalNotification],其中包含移除原因[RemovalCause]、键和值。

    @Test
public void testCacheRemovedNotification() {
CacheLoader<String, String> loader = CacheLoader.from(String::toUpperCase);
RemovalListener<String, String> listener = notification -> {
if (notification.wasEvicted()) {
RemovalCause cause = notification.getCause();
System.out.println("remove cause is :" + cause.toString());
System.out.println("key:" + notification.getKey() + "value:" + notification.getValue());
}
};
LoadingCache<String, String> cache = CacheBuilder.newBuilder()
.maximumSize(3)
.removalListener(listener)// 添加删除监听
.build(loader);
cache.getUnchecked("lhx");
cache.getUnchecked("wangwang");
cache.getUnchecked("guava");
cache.getUnchecked("test");
cache.getUnchecked("test1");
}

输出

remove cause is :SIZE
key:lhxvalue:LHX
remove cause is :SIZE
key:wangwangvalue:WANGWANG

  警告:默认情况下,监听器方法是在移除缓存时同步调用的。因为缓存的维护和请求响应通常是同时进行的,代价高昂的监听器方法在同步模式下会拖慢正常的缓存请求。在这种情况下,你可以使用RemovalListeners.asynchronous(RemovalListener, Executor)把监听器装饰为异步操作。

  请注意,RemovalListener抛出的任何异常都会在记录到日志后被丢弃[swallowed]。

 

 

007-guava 缓存的更多相关文章

  1. Guava缓存器源码分析——删除消息

    Guava缓存器的删除消息机制 测试代码——             LoadingCache<String, Integer> cache = CacheBuilder.newBuild ...

  2. Guava缓存器源码分析——缓存统计器

    Guava缓存器统计器实现: 全局统计器——         1.CacheBuilder的静态成员变量Supplier<StatsCounter> CACHE_STATS_COUNTER ...

  3. guava缓存底层实现

    摘要 guava的缓存相信很多人都有用到, Cache<String, String> cache = CacheBuilder.newBuilder() .expireAfterWrit ...

  4. Google Guava缓存实现接口的限流

    一.项目背景 最近项目中需要进行接口保护,防止高并发的情况把系统搞崩,因此需要对一个查询接口进行限流,主要的目的就是限制单位时间内请求此查询的次数,例如1000次,来保护接口. 参考了 开涛的博客聊聊 ...

  5. springboot集成Guava缓存

    很久没有写博客了,这段时间一直忙于看论文,写论文,简直头大,感觉还是做项目比较舒服,呵呵,闲话不多说,今天学习了下Guava缓存,这跟Redis类似的,但是适用的场景不一样,学习下吧.今天我们主要是s ...

  6. spring中添加google的guava缓存(demo)

    1.pom文件中配置 <dependencies> <dependency> <groupId>org.springframework</groupId> ...

  7. guava缓存设置return null一直报错空指针

    guava缓存设置return null一直报错空指针 因为缓存不允许返回为空

  8. spring boot使用guava缓存

    1.pom中插入依赖: <!--guava缓存cache--> <dependency> <groupId>com.google.guava</groupId ...

  9. guava缓存批量获取的一个坑

    摘要 Guava Cache是Google开源的Java工具集库Guava里的一款缓存工具,一直觉得使用起来比较简单,没想到这次居然还踩了一个坑 背景 功能需求抽象出来很简单,就是将数据库的查询sth ...

  10. guava缓存第一篇

    guava缓存主要有2个接口,Cache和LoadingCache. Cache,全类名是com.google.common.cache.Cache,支持泛型.Cache<K, V>. L ...

随机推荐

  1. 1210 BBS admin后台管理及侧边栏筛选个人站点

    目录 昨日内容 django admin后台管理 使用 建表 用户图片的显示 MEDIA用户配置 查找照片 搭建个人站点 防盗链 新建css文件 侧边栏展示标签 定义分类栏与标签栏 定义时间栏 侧边栏 ...

  2. 创建型模式(过渡模式) 简单工厂模式(Simple Factory)

    简单工厂模式(Simple Factory Pattern)属于类的创建型模式,又叫静态工厂方法模式(Static FactoryMethod Pattern) 是通过专门定义一个类来负责创建其他类的 ...

  3. used to do 与be used to doing /n.

    1.used to do:表示过去的习惯性动作,过去如此,现在不再这样了.常译作“过去常常”.(过去时+动词不定式) He used to play basketball when he was yo ...

  4. 四.Protobuf3 缺省值

    解析消息时,如果编码消息不包含特定的单数元素,则解析对象中的相应字段将设置为该字段的默认值.这些默认值是特定于类型的: 对于字符串,默认值为空字符串. 对于字节,默认值为空字节. 对于布尔,默认值为f ...

  5. 关于Serializable

    1.在Java中,只要一个类实现了java.io.Serializable接口,那么它就可以被序列化. 2.通过ObjectOutputStream和ObjectInputStream对对象进行序列化 ...

  6. mvn的使用和搭建环境

    一 创建一个maven项目必须要下载maven,maven的主要功能有两个 1.可以动态配置项目所依赖的jar包,在maven下载目录下/conf/settings.xml中可以配置本地类存储库,配置 ...

  7. Docker 安装mysql、oracle

    来源:唐山网站优化 Docker 安装mysql.oracle 使用ssh工具登录docker docker 的ip一般默认为192.168.99.100可以通过安装docker-machine之后, ...

  8. How to fix “Internal Diagnostics Hub Exception” in VS 2015?

    This worked for me: Stop the VSHub.exe process Delete the files in %TMP%\VsHub\ Restart the "Vi ...

  9. ip address control获取ip字符串

    1.环境:vs2010 & 默认项目字符集(貌似是unicode) 2.首先为ip address control添加control类型变量m_ipaddressedit, BYTE ips[ ...

  10. [Kubernetes] Defining a Pod with YAML

    1. Define a yml file: nginx.pod.yml: apiVersion: v1 kind: Pod metadata: name: my-nginx labels: app: ...