我们经常会遇到一些本地缓存的场景,比如遍历N条记录,依据记录里面的某个ID,获取到相应的名称,如果遍布的时候逐条去调用外部依赖的服务查询名称,显然会比较慢,如果把获取到的记录保存到本地的缓存里面,先判断本地缓存里面是否有记录存在,如果能够找到记录则直接取本地的数据。显然这样处理的话效率会提升不少。
相对于ConcurrentMap来讲guava cache适用于:
- 你愿意消耗一些内存空间来提升速度
- 你预料到某些键会被查询一次以上
- 缓存中存放的数据总量不会超出内存容量。本地单机缓存不是集中式缓存服务器。
只要你的场景符合其中的一项,那guava cache就适合你。
加载
“获取缓存--如果没有--则计算”【get - if - absent - compute】原子语义
在调用get时传入一个Callable实例。缓存元素也可以通过Cache.put方法直接插入,但自动加载是首选。因为它更容易推断所有缓存内容的一致性。
CacheLoader
LoadingCache是附带CacheLoader构建而成的缓存实现。创建自己的CacheLoader通常只需要简单地实现V load(K key) throws Exception方法。
从LoadingCache查询的正规方式是使用get(K)方法。这个方法要么返回已经缓存的值,要么使用CacheLoader向缓存原子地加载新值。
由于CacheLoader可能抛出异常,LoadingCache.get(K)也声明为抛出ExecutionExceptino异常。
如下:
LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
.maximumSize(1000)
.build(
new CacheLoader<Key, Graph>() {
public Graph load(Key key) throws AnyException { // 手工抛异常
return createExpensiveGraph(key);
}
});
...
try {
// 也需要在get的时候抛出异常
return graphs.get(key);
} catch (ExecutionException e) {
throw new OtherException(e.getCause());
}
如果你定义的CacheLoader没有声明任何检查异常,则可以通过getUnchecked(K)查找缓存;但必须注意,一旦CacheLoader声明了检查型异常,就不可以调用getUnchecked(K).
如下:
LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
.expireAfterAccess(10, TimeUnit.MINUTES)
.build(
new CacheLoader<Key, Graph>() {
public Graph load(Key key) { // no checked exception
return createExpensiveGraph(key);
}
});
...
return graphs.getUnchecked(key);
所有类型的guava cache,不管有没有自动加载功能都支持get(K,Callable(V))方法。这个方法返回缓存中相应的值,或者用给定的Callable运算并把结果加入到缓存中。
在整个加载方法完成前,缓存项相关的可观察状态都不会更改。这个方法实现了这个模式:如果有缓存则返回;否则运算、缓存、然后返回。
public static void main(String[] args) {
String key = "instance_id";
Cache<String,Double> cache = CacheBuilder.newBuilder().maximumSize(1000).build();
int i = 1;
while (i < 5){
i++;
try {
Double value = cache.get(key, new Callable<Double>() {
@Override
public Double call() throws Exception {
System.out.println("hello"); //第一次没有的时候会运算然后添加到缓存里面。后面就直接从缓存里面读取数据.
return 120d;
}
});
System.out.println(value);
} catch (ExecutionException ex) {
System.out.println(ex.getCause());
}
}
}
输出:
hello
120.0
120.0
120.0
120.0
相对于像Cache.asMap().putIfAbsent(K,V)来讲,Cache.get(K,Callable<V>)应该最优先使用。
缓存回收机制
提供了三种回收方式:基于容量回收、定时回收、基于引用回收
基于容量回收
需要使用CacheBuilder.maximumSize(long).
显式清除
个别清除:Cache.invalidate(key)
批量清除:Cache.invalidateAll(keys)
清除所有缓存项:Cache.invalidateAll()
在JDK8里面的实现代码
static Map<String, String> cache2 = new ConcurrentHashMap<>();
public static String getAppProduct(String appName){
// call aone
System.out.println("call aone");
return "hello1";
}
public static void main(String[] args) {
String key = "instance_id";
String result = cache2.computeIfAbsent(key,(item) -> getAppProduct(key));
System.out.println(result);
其实我觉得用jdk8的这种方式也是可以的.