一、簡(jiǎn)介
guava cache是google guava中的一個(gè)內(nèi)存緩存模塊,用于將數(shù)據(jù)緩存到JVM內(nèi)存中.實(shí)際項(xiàng)目開發(fā)中經(jīng)常將一些比較公共或者常用的數(shù)據(jù)緩存起來(lái)方便快速訪問(wèn).
內(nèi)存緩存最常見(jiàn)的就是基于HashMap實(shí)現(xiàn)的緩存,為了解決并發(fā)問(wèn)題也可能也會(huì)用到ConcurrentHashMap等并發(fā)集合,但是內(nèi)存緩存需要考慮很多問(wèn)題,包括并發(fā)問(wèn)題罕容、緩存過(guò)期機(jī)制萌抵、緩存移除機(jī)制、緩存命中統(tǒng)計(jì)率等.
guava cache已經(jīng)考慮到這些問(wèn)題,可以上手即用.通過(guò)CacheBuilder創(chuàng)建緩存舔稀、然后設(shè)置緩存的相關(guān)參數(shù)乳丰、設(shè)置緩存的加載方法等.本例子主要講解guava cache的基本用法,詳細(xì)的說(shuō)明已在代碼中說(shuō)明.
二、代碼示例
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()
//設(shè)置并發(fā)級(jí)別為8内贮,并發(fā)級(jí)別是指可以同時(shí)寫緩存的線程數(shù)
.concurrencyLevel(8)
//設(shè)置緩存容器的初始容量為10
.initialCapacity(10)
//設(shè)置緩存最大容量為100产园,超過(guò)100之后就會(huì)按照LRU最近雖少使用算法來(lái)移除緩存項(xiàng)
.maximumSize(100)
//是否需要統(tǒng)計(jì)緩存情況,該操作消耗一定的性能,生產(chǎn)環(huán)境應(yīng)該去除
.recordStats()
//設(shè)置寫緩存后n秒鐘過(guò)期
.expireAfterWrite(17, TimeUnit.SECONDS)
//設(shè)置讀寫緩存后n秒鐘過(guò)期,實(shí)際很少用到,類似于expireAfterWrite
//.expireAfterAccess(17, TimeUnit.SECONDS)
//只阻塞當(dāng)前數(shù)據(jù)加載線程汞斧,其他線程返回舊值
//.refreshAfterWrite(13, TimeUnit.SECONDS)
//設(shè)置緩存的移除通知
.removalListener(notification -> {
System.out.println(notification.getKey() + " " + notification.getValue() + " 被移除,原因:" + notification.getCause());
})
//build方法中可以指定CacheLoader,在緩存不存在時(shí)通過(guò)CacheLoader的實(shí)現(xiàn)自動(dòng)加載緩存
.build(new DemoCacheLoader());
//模擬線程并發(fā)
new Thread(() -> {
//非線程安全的時(shí)間格式化工具
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();
//緩存狀態(tài)查看
System.out.println(cache.stats().toString());
}
/**
* 隨機(jī)緩存加載,實(shí)際使用時(shí)應(yīng)實(shí)現(xiàn)業(yè)務(wù)的緩存加載邏輯,例如從數(shù)據(jù)庫(kù)獲取數(shù)據(jù)
*/
public static class DemoCacheLoader extends CacheLoader<Integer, String> {
@Override
public String load(Integer key) throws Exception {
System.out.println(Thread.currentThread().getName() + " 加載數(shù)據(jù)開始");
TimeUnit.SECONDS.sleep(8);
Random random = new Random();
System.out.println(Thread.currentThread().getName() + " 加載數(shù)據(jù)結(jié)束");
return "value:" + random.nextInt(10000);
}
}
}
三什燕、策略分析
expireAfterWrite 寫緩存后多久過(guò)期
expireAfterAccess 讀寫緩存后多久過(guò)期
refreshAfterWrite 寫入數(shù)據(jù)后多久過(guò)期,只阻塞當(dāng)前數(shù)據(jù)加載線程,其他線程返回舊值
這幾個(gè)策略時(shí)間可以單獨(dú)設(shè)置,也可以組合配置
expireAfterWrite與refreshAfterWrite單獨(dú)使用與混合使用的策略分析
已知配置條件:
Thread-1 每 3 秒獲取一次緩存id=1的數(shù)據(jù)
Thread-2 每 5 秒獲取一次緩存id=1的數(shù)據(jù)
加載一次緩存加載數(shù)據(jù)耗時(shí) 8 秒
1粘勒、expireAfterWrite單獨(dú)使用
expireAfterWrite=17
Thread-2 加載數(shù)據(jù)開始
Thread-2 加載數(shù)據(jù)結(jié)束
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 加載數(shù)據(jù)開始
Thread-1 加載數(shù)據(jù)結(jié)束
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
說(shuō)明:
啟動(dòng)時(shí)Thread-2加載數(shù)據(jù),此時(shí)緩存中無(wú)數(shù)據(jù),Thread-1阻塞等待Thread-2加載完成數(shù)據(jù). 在設(shè)置的時(shí)間數(shù)據(jù)過(guò)期后Thread-1加載數(shù)據(jù),Thread-2本應(yīng)該01:04:22后的5秒加載數(shù)據(jù),但是Thread-1等待3秒后加載,數(shù)據(jù)加載耗時(shí)8秒,所以Thread-2在01:04:33時(shí)加載數(shù)據(jù)成功.
結(jié)論:
當(dāng)其他線程在加載數(shù)據(jù)的時(shí)候,當(dāng)前線程會(huì)一直阻塞等待其他線程加載數(shù)據(jù)完成.
2、refreshAfterWrite單獨(dú)使用
refreshAfterWrite=17
Thread-2 加載數(shù)據(jù)開始
Thread-2 加載數(shù)據(jù)結(jié)束
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 加載數(shù)據(jù)開始
Thread-2 01:13:52 value:551
Thread-2 01:13:57 value:551
Thread-1 加載數(shù)據(jù)結(jié)束
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
說(shuō)明:
啟動(dòng)時(shí)Thread-2加載數(shù)據(jù),此時(shí)緩存中無(wú)數(shù)據(jù),Thread-1阻塞等待Thread-2加載完成數(shù)據(jù). 在設(shè)置的時(shí)間數(shù)據(jù)過(guò)期后Thread-1加載數(shù)據(jù),Thread-2仍然按照策略獲取到舊數(shù)據(jù)成功.
結(jié)論:
當(dāng)沒(méi)有數(shù)據(jù)的時(shí)候,其他線程在加載數(shù)據(jù)的時(shí)候,當(dāng)前線程會(huì)一直阻塞等待其他線程加載數(shù)據(jù)完成;如果有數(shù)據(jù)的情況下其他線程正在加載數(shù)據(jù),當(dāng)前線程返回舊數(shù)據(jù).
3屎即、expireAfterWrite與refreshAfterWrite一起使用情況一
expireAfterWrite=13
refreshAfterWrite=17
Thread-2 加載數(shù)據(jù)開始
Thread-2 加載數(shù)據(jù)結(jié)束
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 加載數(shù)據(jù)開始
Thread-1 加載數(shù)據(jù)結(jié)束
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
說(shuō)明:
啟動(dòng)時(shí)Thread-2加載數(shù)據(jù),此時(shí)緩存中無(wú)數(shù)據(jù),Thread-1阻塞等待Thread-2加載完成數(shù)據(jù). 在設(shè)置的時(shí)間數(shù)據(jù)過(guò)期后Thread-1加載數(shù)據(jù),Thread-2本應(yīng)該01:18:42后的5秒加載數(shù)據(jù),但是Thread-1等待3秒后加載,數(shù)據(jù)加載耗時(shí)8秒,所以Thread-2在01:18:55時(shí)加載數(shù)據(jù)成功.
結(jié)論:
當(dāng)其他線程在加載數(shù)據(jù)的時(shí)候,當(dāng)前線程會(huì)一直阻塞等待其他線程加載數(shù)據(jù)完成,與單獨(dú)使用expireAfterWrite一樣的效果.
4庙睡、expireAfterWrite與refreshAfterWrite一起使用情況二
expireAfterWrite=17
refreshAfterWrite=13
Thread-2 加載數(shù)據(jù)開始
Thread-2 加載數(shù)據(jù)結(jié)束
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 加載數(shù)據(jù)開始
Thread-1 01:20:40 value:1595
Thread-2 加載數(shù)據(jù)結(jié)束
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 加載數(shù)據(jù)開始
Thread-2 01:21:03 value:2277
Thread-1 加載數(shù)據(jù)結(jié)束
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
說(shuō)明:
啟動(dòng)時(shí)Thread-2加載數(shù)據(jù),此時(shí)緩存中無(wú)數(shù)據(jù),Thread-1阻塞等待Thread-2加載完成數(shù)據(jù). 在設(shè)置的時(shí)間數(shù)據(jù)過(guò)期后Thread-2加載數(shù)據(jù),Thread-1仍然按照策略在01:20:40獲取到舊數(shù)據(jù)成功,但是本應(yīng)該01:20:45繼續(xù)獲取一次數(shù)據(jù)但是等到01:20:48才獲取成功.
結(jié)論:
當(dāng)沒(méi)有數(shù)據(jù)的時(shí)候,其他線程在加載數(shù)據(jù)的時(shí)候,當(dāng)前線程會(huì)一直阻塞等待其他線程加載數(shù)據(jù)完成; 如果有數(shù)據(jù)的情況下其他線程正在加載數(shù)據(jù),已經(jīng)超過(guò)refreshAfterWrite設(shè)置時(shí)間但是沒(méi)有超過(guò)expireAfterWrite設(shè)置的時(shí)間時(shí)當(dāng)前線程返回舊數(shù)據(jù). 如果有數(shù)據(jù)的情況下其他線程正在加載數(shù)據(jù),已經(jīng)超過(guò)expireAfterWrite設(shè)置的時(shí)間時(shí)當(dāng)前線程阻塞等待其他線程加載數(shù)據(jù)完成. 這種情況適合與設(shè)置一個(gè)加載緩沖區(qū)的情況,既能保證過(guò)期后加載數(shù)據(jù),又能保證長(zhǎng)時(shí)間沒(méi)訪問(wèn)多個(gè)線程并發(fā)時(shí)獲取到過(guò)期舊數(shù)據(jù)的情況.