代码地址:https://github.com/vikde/demo-guava-cachejava
guava cache是google guava中的一个内存缓存模块,用于将数据缓存到JVM内存中.实际项目开发中常常将一些比较公共或者经常使用的数据缓存起来方便快速访问.git
内存缓存最多见的就是基于HashMap实现的缓存,为了解决并发问题也可能也会用到ConcurrentHashMap等并发集合,可是内存缓存须要考虑不少问题,包括并发问题、缓存过时机制、缓存移除机制、缓存命中统计率等.github
guava cache已经考虑到这些问题,能够上手即用.经过CacheBuilder建立缓存、而后设置缓存的相关参数、设置缓存的加载方法等.本例子主要讲解guava cache的基本用法,详细的说明已在代码中说明.算法
1 package com.vikde.demo.guava.cache; 2 3 import com.google.common.cache.CacheBuilder; 4 import com.google.common.cache.CacheLoader; 5 import com.google.common.cache.LoadingCache; 6 7 import java.text.SimpleDateFormat; 8 import java.util.Date; 9 import java.util.Random; 10 import java.util.concurrent.TimeUnit; 11 12 /** 13 * google guava cache 缓存demo 14 * 15 * @author vikde 16 * @date 2017/12/14 17 */ 18 public class DemoGuavaCache { 19 public static void main(String[] args) throws Exception { 20 LoadingCache<Integer, String> cache = CacheBuilder.newBuilder() 21 //设置并发级别为8,并发级别是指能够同时写缓存的线程数 22 .concurrencyLevel(8) 23 //设置缓存容器的初始容量为10 24 .initialCapacity(10) 25 //设置缓存最大容量为100,超过100以后就会按照LRU最近虽少使用算法来移除缓存项 26 .maximumSize(100) 27 //是否须要统计缓存状况,该操做消耗必定的性能,生产环境应该去除 28 .recordStats() 29 //设置写缓存后n秒钟过时 30 .expireAfterWrite(17, TimeUnit.SECONDS) 31 //设置读写缓存后n秒钟过时,实际不多用到,相似于expireAfterWrite 32 //.expireAfterAccess(17, TimeUnit.SECONDS) 33 //只阻塞当前数据加载线程,其余线程返回旧值 34 //.refreshAfterWrite(13, TimeUnit.SECONDS) 35 //设置缓存的移除通知 36 .removalListener(notification -> { 37 System.out.println(notification.getKey() + " " + notification.getValue() + " 被移除,缘由:" + notification.getCause()); 38 }) 39 //build方法中能够指定CacheLoader,在缓存不存在时经过CacheLoader的实现自动加载缓存 40 .build(new DemoCacheLoader()); 41 42 //模拟线程并发 43 new Thread(() -> { 44 //非线程安全的时间格式化工具 45 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("HH:mm:ss"); 46 try { 47 for (int i = 0; i < 15; i++) { 48 String value = cache.get(1); 49 System.out.println(Thread.currentThread().getName() + " " + simpleDateFormat.format(new Date()) + " " + value); 50 TimeUnit.SECONDS.sleep(3); 51 } 52 } catch (Exception ignored) { 53 } 54 }).start(); 55 56 new Thread(() -> { 57 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("HH:mm:ss"); 58 try { 59 for (int i = 0; i < 10; i++) { 60 String value = cache.get(1); 61 System.out.println(Thread.currentThread().getName() + " " + simpleDateFormat.format(new Date()) + " " + value); 62 TimeUnit.SECONDS.sleep(5); 63 } 64 } catch (Exception ignored) { 65 } 66 }).start(); 67 //缓存状态查看 68 System.out.println(cache.stats().toString()); 69 } 70 71 /** 72 * 随机缓存加载,实际使用时应实现业务的缓存加载逻辑,例如从数据库获取数据 73 */ 74 public static class DemoCacheLoader extends CacheLoader<Integer, String> { 75 @Override 76 public String load(Integer key) throws Exception { 77 System.out.println(Thread.currentThread().getName() + " 加载数据开始"); 78 TimeUnit.SECONDS.sleep(8); 79 Random random = new Random(); 80 System.out.println(Thread.currentThread().getName() + " 加载数据结束"); 81 return "value:" + random.nextInt(10000); 82 } 83 } 84 }
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时加载数据成功.并发
结论:dom
当其余线程在加载数据的时候,当前线程会一直阻塞等待其余线程加载数据完成.ide
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设置的时间时当前线程阻塞等待其余线程加载数据完成. 这种状况适合与设置一个加载缓冲区的状况,既能保证过时后加载数据,又能保证长时间没访问多个线程并发时获取到过时旧数据的状况.