代码地址:https://github.com/vikde/demo-guava-cache

一、简介

guava cache是google guava中的一个内存缓存模块,用于将数据缓存到JVM内存中.实际项目开发中经常将一些比较公共或者常用的数据缓存起来方便快速访问.

内存缓存最常见的就是基于HashMap实现的缓存,为了解决并发问题也可能也会用到ConcurrentHashMap等并发集合,但是内存缓存需要考虑很多问题,包括并发问题、缓存过期机制、缓存移除机制、缓存命中统计率等.

guava cache已经考虑到这些问题,可以上手即用.通过CacheBuilder创建缓存、然后设置缓存的相关参数、设置缓存的加载方法等.本例子主要讲解guava cache的基本用法,详细的说明已在代码中说明.

二、代码示例

 package com.vikde.demo.guava.cache;

 import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache; import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
import java.util.concurrent.TimeUnit; /**
* google guava cache 缓存demo
*
* @author vikde
* @date 2017/12/14
*/
public class DemoGuavaCache {
public static void main(String[] args) throws Exception {
LoadingCache<Integer, String> cache = CacheBuilder.newBuilder()
//设置并发级别为8,并发级别是指可以同时写缓存的线程数
.concurrencyLevel(8)
//设置缓存容器的初始容量为10
.initialCapacity(10)
//设置缓存最大容量为100,超过100之后就会按照LRU最近虽少使用算法来移除缓存项
.maximumSize(100)
//是否需要统计缓存情况,该操作消耗一定的性能,生产环境应该去除
.recordStats()
//设置写缓存后n秒钟过期
.expireAfterWrite(17, TimeUnit.SECONDS)
//设置读写缓存后n秒钟过期,实际很少用到,类似于expireAfterWrite
//.expireAfterAccess(17, TimeUnit.SECONDS)
//只阻塞当前数据加载线程,其他线程返回旧值
//.refreshAfterWrite(13, TimeUnit.SECONDS)
//设置缓存的移除通知
.removalListener(notification -> {
System.out.println(notification.getKey() + " " + notification.getValue() + " 被移除,原因:" + notification.getCause());
})
//build方法中可以指定CacheLoader,在缓存不存在时通过CacheLoader的实现自动加载缓存
.build(new DemoCacheLoader()); //模拟线程并发
new Thread(() -> {
//非线程安全的时间格式化工具
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("HH:mm:ss");
try {
for (int i = 0; i < 15; i++) {
String value = cache.get(1);
System.out.println(Thread.currentThread().getName() + " " + simpleDateFormat.format(new Date()) + " " + value);
TimeUnit.SECONDS.sleep(3);
}
} catch (Exception ignored) {
}
}).start(); new Thread(() -> {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("HH:mm:ss");
try {
for (int i = 0; i < 10; i++) {
String value = cache.get(1);
System.out.println(Thread.currentThread().getName() + " " + simpleDateFormat.format(new Date()) + " " + value);
TimeUnit.SECONDS.sleep(5);
}
} catch (Exception ignored) {
}
}).start();
//缓存状态查看
System.out.println(cache.stats().toString());
} /**
* 随机缓存加载,实际使用时应实现业务的缓存加载逻辑,例如从数据库获取数据
*/
public static class DemoCacheLoader extends CacheLoader<Integer, String> {
@Override
public String load(Integer key) throws Exception {
System.out.println(Thread.currentThread().getName() + " 加载数据开始");
TimeUnit.SECONDS.sleep(8);
Random random = new Random();
System.out.println(Thread.currentThread().getName() + " 加载数据结束");
return "value:" + random.nextInt(10000);
}
}
}

三、策略分析

expireAfterWrite 写缓存后多久过期
expireAfterAccess 读写缓存后多久过期
refreshAfterWrite 写入数据后多久过期,只阻塞当前数据加载线程,其他线程返回旧值 这几个策略时间可以单独设置,也可以组合配置

expireAfterWrite与refreshAfterWrite单独使用与混合使用的策略分析

已知配置条件:
Thread-1 每 3 秒获取一次缓存id=1的数据
Thread-2 每 5 秒获取一次缓存id=1的数据
加载一次缓存加载数据耗时 8 秒

1、expireAfterWrite单独使用

expireAfterWrite=17

Thread-2 加载数据开始
Thread-2 加载数据结束
Thread-1 01:04:07 value:6798
Thread-2 01:04:07 value:6798
Thread-1 01:04:10 value:6798
Thread-2 01:04:12 value:6798
Thread-1 01:04:13 value:6798
Thread-1 01:04:16 value:6798
Thread-2 01:04:17 value:6798
Thread-1 01:04:19 value:6798
Thread-1 01:04:22 value:6798
Thread-2 01:04:22 value:6798
1 value:6798 被移除,原因:EXPIRED
Thread-1 加载数据开始
Thread-1 加载数据结束
Thread-1 01:04:33 value:7836
Thread-2 01:04:33 value:7836
Thread-1 01:04:36 value:7836
Thread-2 01:04:38 value:7836
Thread-1 01:04:39 value:7836

说明:

启动时Thread-2加载数据,此时缓存中无数据,Thread-1阻塞等待Thread-2加载完成数据. 在设置的时间数据过期后Thread-1加载数据,Thread-2本应该01:04:22后的5秒加载数据,但是Thread-1等待3秒后加载,数据加载耗时8秒,所以Thread-2在01:04:33时加载数据成功.

结论:

当其他线程在加载数据的时候,当前线程会一直阻塞等待其他线程加载数据完成.

2、refreshAfterWrite单独使用

refreshAfterWrite=17

Thread-2 加载数据开始
Thread-2 加载数据结束
Thread-1 01:13:32 value:551
Thread-2 01:13:32 value:551
Thread-1 01:13:35 value:551
Thread-2 01:13:37 value:551
Thread-1 01:13:38 value:551
Thread-1 01:13:41 value:551
Thread-2 01:13:42 value:551
Thread-1 01:13:44 value:551
Thread-1 01:13:47 value:551
Thread-2 01:13:47 value:551
Thread-1 加载数据开始
Thread-2 01:13:52 value:551
Thread-2 01:13:57 value:551
Thread-1 加载数据结束
1 value:551 被移除,原因:REPLACED
Thread-1 01:13:58 value:827
Thread-1 01:14:01 value:827
Thread-2 01:14:02 value:827
Thread-1 01:14:04 value:827
Thread-2 01:14:07 value:827

说明:

启动时Thread-2加载数据,此时缓存中无数据,Thread-1阻塞等待Thread-2加载完成数据. 在设置的时间数据过期后Thread-1加载数据,Thread-2仍然按照策略获取到旧数据成功.

结论:

当没有数据的时候,其他线程在加载数据的时候,当前线程会一直阻塞等待其他线程加载数据完成;如果有数据的情况下其他线程正在加载数据,当前线程返回旧数据.

3、expireAfterWrite与refreshAfterWrite一起使用情况一

expireAfterWrite=13

refreshAfterWrite=17

Thread-2 加载数据开始
Thread-2 加载数据结束
Thread-1 01:18:32 value:5901
Thread-2 01:18:32 value:5901
Thread-1 01:18:35 value:5901
Thread-2 01:18:37 value:5901
Thread-1 01:18:38 value:5901
Thread-1 01:18:41 value:5901
Thread-2 01:18:42 value:5901
Thread-1 01:18:44 value:5901
1 value:5901 被移除,原因:EXPIRED
Thread-1 加载数据开始
Thread-1 加载数据结束
Thread-2 01:18:55 value:1300
Thread-1 01:18:55 value:1300
Thread-1 01:18:58 value:1300
Thread-2 01:19:00 value:1300
Thread-1 01:19:01 value:1300

说明:

启动时Thread-2加载数据,此时缓存中无数据,Thread-1阻塞等待Thread-2加载完成数据. 在设置的时间数据过期后Thread-1加载数据,Thread-2本应该01:18:42后的5秒加载数据,但是Thread-1等待3秒后加载,数据加载耗时8秒,所以Thread-2在01:18:55时加载数据成功.

结论:

当其他线程在加载数据的时候,当前线程会一直阻塞等待其他线程加载数据完成,与单独使用expireAfterWrite一样的效果.

4、expireAfterWrite与refreshAfterWrite一起使用情况二

expireAfterWrite=17

refreshAfterWrite=13

Thread-2 加载数据开始
Thread-2 加载数据结束
Thread-1 01:20:25 value:1595
Thread-2 01:20:25 value:1595
Thread-1 01:20:28 value:1595
Thread-2 01:20:30 value:1595
Thread-1 01:20:31 value:1595
Thread-1 01:20:34 value:1595
Thread-2 01:20:35 value:1595
Thread-1 01:20:37 value:1595
Thread-2 加载数据开始
Thread-1 01:20:40 value:1595
Thread-2 加载数据结束
Thread-1 01:20:48 value:2277
1 value:1595 被移除,原因:EXPIRED
Thread-2 01:20:48 value:2277
Thread-1 01:20:51 value:2277
Thread-2 01:20:53 value:2277
Thread-1 01:20:54 value:2277
Thread-1 01:20:57 value:2277
Thread-2 01:20:58 value:2277
Thread-1 01:21:00 value:2277
Thread-1 加载数据开始
Thread-2 01:21:03 value:2277
Thread-1 加载数据结束
Thread-2 01:21:11 value:3750
1 value:2277 被移除,原因:EXPIRED
Thread-1 01:21:11 value:3750
Thread-1 01:21:14 value:3750
Thread-2 01:21:16 value:3750
Thread-1 01:21:17 value:3750
Thread-1 01:21:20 value:3750
Thread-2 01:21:21 value:3750

说明:

启动时Thread-2加载数据,此时缓存中无数据,Thread-1阻塞等待Thread-2加载完成数据. 在设置的时间数据过期后Thread-2加载数据,Thread-1仍然按照策略在01:20:40获取到旧数据成功,但是本应该01:20:45继续获取一次数据但是等到01:20:48才获取成功.

结论:

当没有数据的时候,其他线程在加载数据的时候,当前线程会一直阻塞等待其他线程加载数据完成; 如果有数据的情况下其他线程正在加载数据,已经超过refreshAfterWrite设置时间但是没有超过expireAfterWrite设置的时间时当前线程返回旧数据. 如果有数据的情况下其他线程正在加载数据,已经超过expireAfterWrite设置的时间时当前线程阻塞等待其他线程加载数据完成. 这种情况适合与设置一个加载缓冲区的情况,既能保证过期后加载数据,又能保证长时间没访问多个线程并发时获取到过期旧数据的情况.

google guava cache缓存基本使用讲解的更多相关文章

  1. (翻译)Google Guava Cache

    翻译自Google Guava Cache This Post is a continuation of my series on Google Guava, this time covering G ...

  2. spring boot guava cache 缓存学习

    http://blog.csdn.net/hy245120020/article/details/78065676 ****************************************** ...

  3. Google Guava Cache 全解析

    Google guava工具类的介绍和使用https://blog.csdn.net/wwwdc1012/article/details/82228458 LoadingCache缓存使用(Loadi ...

  4. Google guava cache源码解析1--构建缓存器(1)

    此文已由作者赵计刚授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 1.guava cache 当下最常用最简单的本地缓存 线程安全的本地缓存 类似于ConcurrentHas ...

  5. 第二章 Google guava cache源码解析1--构建缓存器

    1.guava cache 当下最常用最简单的本地缓存 线程安全的本地缓存 类似于ConcurrentHashMap(或者说成就是一个ConcurrentHashMap,只是在其上多添加了一些功能) ...

  6. [Google Guava]学习--缓存cache

    适用性 缓存在很多情况下非常实用.例如,计算或检索一个值的代价很高,并且对同样的输入需要不止一次获取值的时候,就应当考虑使用缓存. Guava Cache与ConcurrentMap很相似,但也不完全 ...

  7. Google guava cache源码解析1--构建缓存器(3)

    此文已由作者赵计刚授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 下面介绍在LocalCache(CacheBuilder, CacheLoader)中调用的一些方法: Ca ...

  8. Java内存缓存-通过Google Guava创建缓存

    谷歌Guava缓存 Guava介绍 Guava是Google guava中的一个内存缓存模块,用于将数据缓存到JVM内存中.实际项目开发中经常将一些公共或者常用的数据缓存起来方便快速访问. Guava ...

  9. 同时使用Redis缓存和Google Guava本地缓存注意事项(深拷贝和浅拷贝)

    目录 1.问题场景及说明 2.Redis 缓存是深拷贝 3.Guava本地缓存直接获取则是浅拷贝 4.如何实现Guava获取本地缓存是深拷贝? 1.问题场景及说明 系统中同时使用 Redis 缓存和 ...

随机推荐

  1. uploadify 配置后,页面显示无效果

    uploadify使用的是Flash版本 谷歌浏览器:默认没有开启Flash,进行如下图设置即可

  2. Snail’s trouble

    Snail’s trouble Time Limit: 20000/10000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) T ...

  3. Codeforces 376C. Socks

    C. Socks time limit per test 2 seconds memory limit per test 256 megabytes input standard input outp ...

  4. JAVA提高十二:HashMap深入分析

    首先想说的是关于HashMap源码的分析园子里面应该有很多,并且都是分析得很不错的文章,但是我还是想写出自己的学习总结,以便加深自己的理解,因此就有了此文,另外因为小孩过来了,因此更新速度可能放缓了, ...

  5. 技嘉 gigabyte b75m d3v 主板 定时开机无效问题解决

    BIOS 里面设置定时开机后发现到点并没有正常启动~~~  百思不得解.后来发现原来是WIN8系统下的控制面板的关机并非正常关机,而是不保存设置的非正常关机,在开始菜单右键——关闭或注销——关闭计算机 ...

  6. Javascript parseFloat、parseDouble类型转换,数值加减,四舍五入

    <script language="JavaScript">var a = "0.11";var b = "0.2801";va ...

  7. 阿里云ecs初始化磁盘后远程连接不到服务器

    阿里云初始化磁盘后远程连接不到服务器 报错: WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! ... 原因:阿里云ecs第一次链接服务器之后会在本地电 ...

  8. 基于Visual Studio 2010 阐述C#4个特性

    Csharp4.0与以往版本基础体现了强大的性能优势,主要体现在以下四个方面: 1. 通过委托成员来实现接口 在C# 4.0中可以通过委托来实现某个成员的接口,例如下面的代码: public clas ...

  9. 《天书夜读:从汇编语言到windows内核编程》八 文件操作与注册表操作

    1)Windows运用程序的文件与注册表操作进入R0层之后,都有对应的内核函数实现.在windows内核中,无论打开的是文件.注册表或者设备,都需要使用InitializeObjectAttribut ...

  10. 线性回归,附tensorflow实现

    本文同步自:https://zhuanlan.zhihu.com/p/30738405 本文旨在通过介绍线性回归来引出一些基本概念:h(x),J(θ),梯度下降法 有一组数据: x=[1,2,3,4, ...