Guava Cache与ConcurrentMap很相似,但也不完全一样。最基本的区别是ConcurrentMap会一直保存所有添加的元素,直到显式地移除。相对地,Guava Cache为了限制内存占用,通常都设定为自动回收元素。在某些场景下,尽管LoadingCache 不回收元素,它也是很有用的,因为它会自动加载缓存。
通常来说,Guava Cache适用于:
你愿意消耗一些内存空间来提升速度。
你预料到某些键会被查询一次以上。
缓存中存放的数据总量不会超出内存容量。(Guava Cache是单个应用运行时的本地缓存。它不把数据存放到文件或外部服务器。如果这不符合你的需求,请尝试
Memcached
这类工具)
如果你的场景符合上述的每一条,Guava Cache就适合你。
如同范例代码展示的一样,Cache实例通过CacheBuilder生成器模式获取,但是自定义你的缓存才是最有趣的部分。
注
:如果你不需要Cache中的特性,使用ConcurrentHashMap有更好的内存效率——但Cache的大多数特性都很难基于旧有的ConcurrentMap复制,甚至根本不可能做到。
Guava Cache的使用示例
使用缓存时,最常遇到的场景需要就是:
"获取缓存-如果没有-则计算"[get-if-absent-compute]的原子语义.
具体含义:
从缓存中取。
缓存中存在该数据,直接返回;
缓存中不存在该数据,从数据源中取。
数据源中存在该数据,放入缓存,并返回;
数据源中不存在该数据,返回空。
三种基于时间的清理或刷新缓存数据的方式:
expireAfterAccess: 当缓存项在指定的时间段内没有被读或写就会被回收。
expireAfterWrite:当缓存项在指定的时间段内没有更新就会被回收。
refreshAfterWrite:当缓存项上一次更新操作之后的多久会被刷新。
一、
定时过期
LoadingCache<String, Object> caches = CacheBuilder.newBuilder()
.maximumSize(100)
.expireAfterWrite(30, TimeUnit.MINUTES)
.build(new CacheLoader<String, Object>() {
@Override
public Object load(String key) throws Exception {
return generateValueByKey(key);
try {
System.out.println(caches.get("key-zorro"));
} catch (ExecutionException e) {
e.printStackTrace();
如代码所示新建了名为caches的一个缓存对象,定义了缓存大小、过期时间及缓存值生成方法。maximumSize定义了缓存的容量大小,当缓存数量即将到达容量上线时,则会进行缓存回收,回收最近没有使用或总体上很少使用的缓存项。需要注意的是在接近这个容量上限时就会发生,所以在定义这个值的时候需要视情况适量地增大一点。
另外通过expireAfterWrite这个方法定义了缓存的过期时间,写入十分钟之后过期。
在build方法里,传入了一个CacheLoader对象,重写了其中的load方法。当获取的缓存值不存在或已过期时,则会调用此load方法,进行缓存值的计算。
二、定时刷新
LoadingCache<String, Object> caches = CacheBuilder.newBuilder()
.maximumSize(100)
.refreshAfterWrite(10, TimeUnit.MINUTES)
.build(new CacheLoader<String, Object>() {
@Override
public Object load(String key) throws Exception {
return generateValueByKey(key);
try {
System.out.println(caches.get("key-zorro"));
} catch (ExecutionException e) {
e.printStackTrace();
每隔十分钟缓存值则会被刷新。
有一个需要注意也很难处理的地方,这里的定时并不是真正意义上的定时。Guava cache的刷新需要依靠用户请求线程,让该线程去进行load方法的调用,所以如果一直没有用户尝试获取该缓存值,则该缓存也并不会刷新。
三、异步刷新
ListeningExecutorService backgroundRefreshPools =
MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(20));
LoadingCache<String, Object> caches = CacheBuilder.newBuilder()
.maximumSize(100)
.refreshAfterWrite(10, TimeUnit.MINUTES)
.build(new CacheLoader<String, Object>() {
@Override
public Object load(String key) throws Exception {
return generateValueByKey(key);
@Override
public ListenableFuture<Object> reload(String key,
Object oldValue) throws Exception {
return backgroundRefreshPools.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
return generateValueByKey(key);
try {
System.out.println(caches.get("key-zorro"));
} catch (ExecutionException e) {
e.printStackTrace();
当缓存的key很多时,高并发条件下大量线程同时获取不同key对应的缓存,此时依然会造成大量线程阻塞,并且给数据库带来很大压力。这个问题的解决办法就是将刷新缓存值的任务交给后台线程,所有的用户请求线程均返回旧的缓存值,这样就不会有用户线程被阻塞了。
可以看到防缓存穿透和防用户线程阻塞都是依靠返回旧值来完成的。所以如果没有旧值,同样会全部阻塞,因此应视情况尽量在系统启动时将缓存内容加载到内存中。
在刷新缓存时,如果generateValueByKey方法出现异常或者返回了null,此时旧值不会更新。
题外话:在使用内存缓存时,切记拿到缓存值之后不要在业务代码中对缓存直接做修改,因为此时拿到的对象引用是指向缓存真正的内容的。如果需要直接在该对象上进行修改,则在获取到缓存值后拷贝一份副本,然后传递该副本,进行修改操作
封装后的的缓存抽象类实现了刷新时间、时间单位、定时刷新及初始化缓存值等方法。实现类只需要设置自己的缓存时间等信息,实现preload()方法加载缓存数据,实现getCacheValue()方法将缓存数据添加到缓存中。
抽象类代码如下:
package com.lenchy.lms.util.cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListenableFutureTask;
import com.homolo.datamodel.manager.EntityManager;
import com.homolo.framework.setup.Initializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.task.TaskExecutor;
import javax.validation.constraints.NotNull;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
* 数据缓存抽象类
* @param <T> 缓存值的类型
public abstract class BaseDataCache<T> extends Initializer {
private static final Logger LOGGER = LoggerFactory.getLogger(BaseDataCache.class);
private static final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(10);
@Autowired
private TaskExecutor taskExecutor;
@Autowired
protected EntityManager entityManager;
private LoadingCache<String, T> cache;
* @return 时间
protected abstract long duration();
* @return 时间单位
protected abstract TimeUnit unit();
* 根据key生成缓存值的方法
* @param key key
* @return 缓存值
protected abstract T getCacheValue(String key);
* 缓存到期是否自动刷新
protected boolean refresh() {
return false;
* 预加载缓存数据,系统启动后执行
* @return Runnable 返回 null 表示不执行预加载
protected Runnable preload() {
return null;
* 获取缓存值,请在初始化完成之后调用
* @param key key
* @return 缓存值,异常时返回 getCacheValue()
public T get(String key) {
try {
return cache.get(key);
} catch (ExecutionException e) {
LOGGER.error("get cache error", e);
return getCacheValue(key);
@Override
public void initialize() {
long duration = duration();
TimeUnit unit = unit();
cache = CacheBuilder.newBuilder().refreshAfterWrite(duration(), unit())
.build(new CacheLoader<String, T>() {
@Override
public T load(@NotNull String key) {
return getCacheValue(key);
@Override
public ListenableFuture<T> reload(final String key, T oldValue) throws Exception {
ListenableFutureTask<T> task = ListenableFutureTask.create(new Callable<T>() {
public T call() {
return getCacheValue(key);
taskExecutor.execute(task);
return task;
Runnable preload = preload();
if (preload != null) {
LOGGER.info("preload {}", this.getClass().getSimpleName());
taskExecutor.execute(preload());
if (refresh()) {
executorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
for (String key : cache.asMap().keySet()) {
cache.refresh(key);
}, duration, duration, unit);
@Override
public int getPhase() {
return 20000;
简单实现类
@Component
public class Statistic4JusticeCache extends BaseDataCache<Statistic4JusticeCache.JusticeCache> {
@Autowired
private JusticeBureauManager justiceBureauManager;
@Autowired
protected NationalDataCountUtil nationalDataStatisticsUtil;
@Autowired
private JusticeBureauFilter justiceBureauFilter;
@Override
protected long duration() {
return 1;
@Override
protected TimeUnit unit() {
return TimeUnit.DAYS;
@Override
protected boolean refresh() {
return true;
@Override
protected Runnable preload() {
return new Runnable() {
@Override
public void run() {
List<String> cacheKwys = new ArrayList<>();
cacheKwys.add( "first" );
cacheKwys.add( "second" );
cacheKwys.add( "third" );
for (String key : cacheKwys) {
get(key);
@Override
protected Statistic4JusticeCache.JusticeCache getCacheValue(String key) {
JusticeCache cache = new JusticeCache();
cache.setFirstList(managerGetList(key));
cache.setSecondList(getZoneList(key));
cache.setThirdMap(getLawyerModifymap(key));
return cache;
public static class JusticeCache {
private List firstList;
private List secondList;
private Map thirdMap;
public List getFirstList() {
return firstList;
public void setFirstList(List firstList) {
this.firstList = firstList;
public List getSecondlist() {
return secondList;
public void setSecondList(List secondList) {
this.secondList = secondList;
public Map getThirdMap() {
return thirdMap;
public void setThirdMap(Map thirdMap) {
this.thirdMap = thirdMap;
public List<String> getFirstList(){
List first = new ArrayList<>();
first.add("first1");
first.add("first2");
return first;
public List<String> getSecondlist(){
List second = new ArrayList<>();
first.add("second1");
first.add("second2");
return first;
public Map<String,String> getThirdMap(){
Map<String, String> third = new HashMap<String, String>();
third.put( "third1" , "third1" );
third.put( "third2" , "third2" );
return third;