前言:
Guava 学习笔记,参考自Google Guava官方文档。
Guava Cache
文章目录
Cache的回收机制
既然是缓存,那么总会存在没有足够的内存缓存所有数据。Cache提供三大类回收机制。
基于容量回收
如果要规定缓存项的数目不超过固定值,只需使用
CacheBuilder.maximumSize(long)
]。缓存将尝试回收最近没有使用或总体上很少使用的缓存项。——警告:在缓存项的数目达到限定值之前,缓存就可能进行回收操作——通常来说,这种情况发生在缓存项的数目逼近限定值时。
基于定时回收
CacheBuilder
提供两种定时回收的方法:
expireAfterAccess(long, TimeUnit)
:缓存项在给定时间内没有被读/写访问,则回收。请注意这种缓存的回收顺序和基于大小回收一样。expireAfterWrite(long, TimeUnit)
:缓存项在给定时间’'内没有被写访问(创建或覆盖),则回收。如果认为缓存数据总是在固定时候后变得陈旧不可用,这种回收方式是可取的。
基于引用类型回收
通过使用弱引用的键、或弱引用的值、或软引用的值,Guava Cache可以把缓存设置为允许垃圾回收:
CacheBuilder.weakKeys()
:使用弱引用存储键。当键没有其它(强或软)引用时,缓存项可以被垃圾回收。因为垃圾回收仅依赖恒等式(),使用弱引用键的缓存用而不是equals比较键。- CacheBuilder.weakValue():使用弱引用存储值。当值没有其它(强或软)引用时,缓存项可以被垃圾回收。因为垃圾回收仅依赖恒等式(),使用弱引用值的缓存用而不是equals比较值。
CacheBuilder.softValues()
:使用软引用存储值。软引用只有在响应内存需要时,才按照全局最近最少使用的顺序回收。考虑到使用软引用的性能影响,我们通常建议使用更有性能预测性的缓存大小限定(见上文,基于容量回收)。使用软引用值的缓存同样用==而不是equals比较值。
具体功能使用
加载 CacheLoader
创建CacheLoader实现load(方法即可。当使用get()方法的时候,要么返回容器中已经缓存的值,要么就是使用CacheLoader想缓存原子的加载新值。、
如果部分场景需要使用getAll(),可以重载CacheLoader.loadAll()提高效率。若不重载则会单独调用CacheLoader.load()进行加载。
LoadingCache<Key, Object> graphs = CacheBuilder.newBuilder()
.build(
//
new CacheLoader<Key, Object>() {
public Graph load(Key key) throws AnyException {
return getObjectFromDb(key);
}
});
Callable
方便自定义每次的获取逻辑, 原理跟CacheLoader.load()一致。 如果缓存中不存在,则调用callable()来进行获取放入缓存中。
Cache<Key, Graph> cache = CacheBuilder.newBuilder()
.maximumSize(1000)
.build(); // look Ma, no CacheLoader
...
try {
// If the key wasn't in the "easy to compute" group, we need to
// do things the hard way.
cache.get(key, new Callable<Key, Graph>() {
@Override
public Value call() throws AnyException {
return doThingsTheHardWay(key);
}
});
} catch (ExecutionException e) {
throw new OtherException(e.getCause());
}
显式插入
使用cache.put(key, value)
方法可以直接向缓存中插入值,这会直接覆盖掉给定键之前映射的值。使用Cache.asMap()
视图提供的任何方法也能修改缓存。但请注意,asMap视图的任何方法都不能保证缓存项被原子地加载到缓存中。进一步说,asMap视图的原子运算在Guava Cache的原子加载范畴之外,所以相比于Cache.asMap().putIfAbsent(K, V),Cache.get(K, Callable<V>)
应该总是优先使用。 即更多的依赖自动加载而不是手动的进行添加。
元素移除监听器 removalListener
通过CacheBuilder.removalListener(RemovalListener)
你可以声明一个监听器,以便缓存项被移除时做一些额外操作。
asMap视图
asMap视图提供了缓存的ConcurrentMap形式,但asMap视图与缓存的交互需要注意:
- cache.asMap()包含当前所有加载到缓存的项。因此相应地,cache.asMap().keySet()包含当前所有已加载键;
- asMap().get(key)实质上等同于cache.getIfPresent(key),而且不会引起缓存项的加载。这和Map的语义约定一致。
- 所有读写操作都会重置相关缓存项的访问时间,包括Cache.asMap().get(Object)方法和Cache.asMap().put(K, V)方法,但不包括Cache.asMap().containsKey(Object)方法,也不包括在Cache.asMap()的集合视图上的操作。比如,遍历Cache.asMap().entrySet()不会重置缓存项的读取时间。
测试代码
package com.justloseit.common.test.guavaTest.guavaCache;
import com.google.common.base.Optional;
import com.google.common.cache.*;
import com.google.common.collect.ImmutableMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* @Classname CacheTest
* @Description guava缓存 Test
* @Author lqhao
* @Date 2021/8/16 15:48
* @Version 1.0
*/
public class CacheTest {
/**
* 移除监听器
*/
static RemovalListener<Integer, Student> MY_LISTENER_01 = removal -> {
Student student = removal.getValue();
System.out.println(student.getName()+"被移除了");
};
/**
* 异步移除监听器 需要添加异步执行的线程池
*/
static RemovalListener<Integer, Student> MY_LISTENER_02=RemovalListeners.asynchronous(removal -> {
Student student = removal.getValue();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(student.getName()+"被异步移除了");
}, Executors.newSingleThreadExecutor());
/**
* Guava缓存
*/
static LoadingCache<Integer, Student> studentCaches = CacheBuilder.newBuilder().softValues()
.maximumSize(1)// 固定容器的大小
.expireAfterWrite(10, TimeUnit.MINUTES)//缓存项在给定时间内没有被写访问(创建或覆盖),则回收
// .removalListener(MY_LISTENER_01) //同步
.removalListener(MY_LISTENER_02) //异步
.build(
//缓存中不存在则主动的进行加载
new CacheLoader<Integer, Student>() {
@Override
public Student load(Integer key) {
Optional<Student> optional=Student.get(key);
if (optional.isPresent()){
return optional.get();
}else{
return Student.valueOf(key, 18, "awake");
}
}
});
/**
* 主函数
* @param args
* @throws ExecutionException
*/
public static void main(String[] args) throws ExecutionException {
System.out.println(studentCaches.get(1));
System.out.println(studentCaches.get(0));
System.out.println(studentCaches.size());
}
static class Student{
private int id;
private int age;
private String name;
private static ImmutableMap<Integer, Student> studentImmutableMap=ImmutableMap.of(
1, Student.valueOf(1, 18, "lqhao"),
2, Student.valueOf(2, 19, "zpwen"),
3, Student.valueOf(3, 20, "qyyi")
);
public static Optional<Student> get(Integer id) {
Student student=studentImmutableMap.get(id);
return Optional.fromNullable(student);
}
public static Student valueOf(int id, int age, String name) {
Student student=new Student();
student.id=id;
student.age=age;
student.name=name;
return student;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", age=" + age +
", name='" + name + '\'' +
'}';
}
}
}