在多线程高并发场景中往往是离不开cache的,需要根据不同的应用场景来需要选择不同的cache,比如分布式缓存如redis、memcached,还有本地(进程内)缓存如ehcache、GuavaCache。之前用spring cache的时候集成的是ehcache,但接触到GuavaCache之后,被它的简单、强大、及轻量级所吸引。它不需要配置文件,使用起来和ConcurrentHashMap一样简单,而且能覆盖绝大多数使用cache的场景需求!
GuavaCache是google开源java类库Guava的其中一个模块,在maven工程下使用可在pom文件加入如下依赖:
com.google.guava
guava
19.0
先说说一般的cache都会实现的基础功能包括:
提供一个存储缓存的容器,该容器实现了存放(Put)和读取(Get)缓存的接口供外部调用。 缓存通常以
在GuavaCache中缓存的容器被定义为接口Cache
final static Cache cache = CacheBuilder.newBuilder()
//设置cache的初始大小为10,要合理设置该值
.initialCapacity(10)
//设置并发数为5,即同一时间最多只能有5个线程往cache执行写入操作
.concurrencyLevel(5)
//设置cache中的数据在写入之后的存活时间为10秒
.expireAfterWrite(10, TimeUnit.SECONDS)
//构建cache实例
.build();
据说GuavaCache的实现是基于ConcurrentHashMap的,因此上面的构造过程所调用的方法,通过查看其官方文档也能看到一些类似的原理。比如通过initialCapacity(5)定义初始值大小,要是定义太大就好浪费内存空间,要是太小,需要扩容的时候就会像map一样需要resize,这个过程会产生大量需要gc的对象,还有比如通过concurrencyLevel(5)来限制写入操作的并发数,这和ConcurrentHashMap的锁机制也是类似的(ConcurrentHashMap读不需要加锁,写入需要加锁,每个segment都有一个锁)。
接下来看看Cache提供哪些方法(只列了部分常用的):
/**
* 该接口的实现被认为是线程安全的,即可在多线程中调用
* 通过被定义单例使用
*/
public interface Cache {
/**
* 通过key获取缓存中的value,若不存在直接返回null
*/
V getIfPresent(Object key);
/**
* 通过key获取缓存中的value,若不存在就通过valueLoader来加载该value
* 整个过程为 "if cached, return; otherwise create, cache and return"
* 注意valueLoader要么返回非null值,要么抛出异常,绝对不能返回null
*/
V get(K key, Callable extends V> valueLoader) throws ExecutionException;
/**
* 添加缓存,若key存在,就覆盖旧值
*/
void put(K key, V value);
/**
* 删除该key关联的缓存
*/
void invalidate(Object key);
/**
* 删除所有缓存
*/
void invalidateAll();
/**
* 执行一些维护操作,包括清理缓存
*/
void cleanUp();
}
使用过程还是要认真查看官方的文档,以下Demo简单的展示了Cache的写入,读取,和过期清除策略是否生效:
public static void main(String[] args) throws Exception {
cache.put(1, "Hi");
for(int i=0 ;i<100 ;i++) {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
System.out.println(sdf.format(new Date())
+ " key:1 ,value:"+cache.getIfPresent(1));
Thread.sleep(1000);
}
}
public class CacheService {
static Cache cache = CacheBuilder.newBuilder()
.expireAfterWrite(5, TimeUnit.SECONDS)
.build();
public static void main(String[] args) throws Exception {
new Thread() { //monitor
public void run() {
while(true) {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
System.out.println(sdf.format(new Date()) +" size: "+cache.size());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
}
};
}.start();
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
cache.put(1, "Hi");
System.out.println("write key:1 ,value:"+cache.getIfPresent(1));
Thread.sleep(10000);
// when write ,key:1 clear
cache.put(2, "bbb");
System.out.println("write key:2 ,value:"+cache.getIfPresent(2));
Thread.sleep(10000);
// when read other key ,key:2 do not clear
System.out.println(sdf.format(new Date())
+" after write, key:1 ,value:"+cache.getIfPresent(1));
Thread.sleep(2000);
// when read same key ,key:2 clear
System.out.println(sdf.format(new Date())
+" final, key:2 ,value:"+cache.getIfPresent(2));
}
}
00:34:17 size: 0
write key:1 ,value:Hi
00:34:19 size: 1
00:34:21 size: 1
00:34:23 size: 1
00:34:25 size: 1
write key:2 ,value:bbb
00:34:27 size: 1
00:34:29 size: 1
00:34:31 size: 1
00:34:33 size: 1
00:34:35 size: 1
00:34:37 after write, key:1 ,value:null
00:34:37 size: 1
00:34:39 final, key:2 ,value:null
00:34:39 size: 0
官方github
官方文档中文翻译