代码地址: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. css基础语法一(选择器与css导入方式)

    页面中,所有的CSS代码,需要写入到<style></style>标签中.style标签的type属性应该选择text/css,但是type属性可以省略. CSS修改页面中的所 ...

  2. html2cavans

    简介:http://www.jianshu.com/p/6a07e974a7e8 下载:https://github.com/niklasvh/html2canvas/releases C#代理git ...

  3. 串口接收模块(verilog) 波特率115200

    我来分享一下uart协议之接收verilog代码 顶层实例化 `timecale 1ns / 1ps////////////////////////////////////////////////// ...

  4. CentOS6.8系统下,ecipse下进行编辑操作,意外退出

    错误情况:centos下打开eclipse软件,点击*.java或者*.pom软件卡死,命令行终端报错误信息,稍后eclipse自动退出. 错误信息: Java: cairo-misc.c:380: ...

  5. jQuery DataTables 获取选中行数据

    如题 想获取操作 DataTables 获取选中行数据 案1.主要是利用 js  getElementsByTagName 函数 然后对获取到的tr 进行操作  如下 function getChec ...

  6. C#中一些默认的预定义属性

    C#中一些默认的预定义属性,见下表: 预定义的属性 有效目标 说明 AttributeUsage Class 指定另一个属性类的有效使用方式 CLSCompliant 全部 指出程序元素是否与CLS兼 ...

  7. 安装scrapy框架的常见问题及其解决方法

    下面小编讲一下自己在windows10安装及配置Scrapy中遇到的一些坑及其解决的方法,现在总结如下,希望对大家有所帮助. 常见问题一:pip版本需要升级 如果你的pip版本比较老,可能在安装的过程 ...

  8. KICKSTART无人值守安装 - (字符界面操作)

    kickstart 部署 1.1 kickstart简介说明 1.1.1 pxe工作过程(图) 1.1.2 kickstart具体过程(图) 1.2 kickstart无人值守部署 1.2.1 系统环 ...

  9. 秒表计时器以及Stopwatch

    Stopwatch:秒表计时器,用来记录程序的运行时间,通常用来测试代码在时间上的执行效率.(需要引用:System.Diagnostics.) Stopwatch sw=new Stopwatch( ...

  10. 十、VueJs 填坑日记之在项目中使用Amaze UI

    上一篇博文,我们把jQuery集成到了项目中,今天我们来集成Amaze ui(妹子UI).先来介绍一下妹子UI.Amaze UI 含近 20 个 CSS 组件.20 余 JS 组件,更有多个包含不同主 ...