代码地址: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 秒
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 时加载数据成功.
结论:
当其他线程在加载数据的时候, 当前线程会一直阻塞等待其他线程加载数据完成.
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 仍然按照策略获取到旧数据成功.
结论:
当没有数据的时候, 其他线程在加载数据的时候, 当前线程会一直阻塞等待其他线程加载数据完成; 如果有数据的情况下其他线程正在加载数据, 当前线程返回旧数据.
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 一样的效果.
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 设置的时间时当前线程阻塞等待其他线程加载数据完成. 这种情况适合与设置一个加载缓冲区的情况, 既能保证过期后加载数据, 又能保证长时间没访问多个线程并发时获取到过期旧数据的情况.
来源: http://www.cnblogs.com/vikde/p/8045226.html