google guava cache緩存基本使用講解

代碼地址:https://github.com/vikde/demo-guava-cachejava

1、簡介

guava cache是google guava中的一個內存緩存模塊,用於將數據緩存到JVM內存中.實際項目開發中常常將一些比較公共或者經常使用的數據緩存起來方便快速訪問.git

內存緩存最多見的就是基於HashMap實現的緩存,爲了解決併發問題也可能也會用到ConcurrentHashMap等併發集合,可是內存緩存須要考慮不少問題,包括併發問題、緩存過時機制、緩存移除機制、緩存命中統計率等.github

guava cache已經考慮到這些問題,能夠上手即用.經過CacheBuilder建立緩存、而後設置緩存的相關參數、設置緩存的加載方法等.本例子主要講解guava cache的基本用法,詳細的說明已在代碼中說明.算法

 

2、代碼示例

 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 }

 

3、策略分析

expireAfterWrite 寫緩存後多久過時
expireAfterAccess 讀寫緩存後多久過時
refreshAfterWrite 寫入數據後多久過時,只阻塞當前數據加載線程,其餘線程返回舊值

這幾個策略時間能夠單獨設置,也能夠組合配置

expireAfterWrite與refreshAfterWrite單獨使用與混合使用的策略分析數據庫

已知配置條件:
Thread-1 每 3 秒獲取一次緩存id=1的數據
Thread-2 每 5 秒獲取一次緩存id=1的數據
加載一次緩存加載數據耗時 8 秒

一、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時加載數據成功.併發

結論:dom

當其餘線程在加載數據的時候,當前線程會一直阻塞等待其餘線程加載數據完成.ide

二、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仍然按照策略獲取到舊數據成功.

結論:

當沒有數據的時候,其餘線程在加載數據的時候,當前線程會一直阻塞等待其餘線程加載數據完成;若是有數據的狀況下其餘線程正在加載數據,當前線程返回舊數據.

三、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同樣的效果.

四、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設置的時間時當前線程阻塞等待其餘線程加載數據完成. 這種狀況適合與設置一個加載緩衝區的狀況,既能保證過時後加載數據,又能保證長時間沒訪問多個線程併發時獲取到過時舊數據的狀況.

相關文章
相關標籤/搜索