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;