網站首頁 編程語言 正文
LoadingCache基礎
LoadingCacheDemo
LoadingCache是Guava中一個提供自動加載功能的緩存接口。它允許咱們通過一個CacheLoader來指定如何加載緩存。這就意味著,當咱們嘗試從緩存中讀取一個值,如果這個值不存在,LoadingCache就會自動調用預定義的加載機制去獲取數據,然后將其加入到緩存中。
public class LoadingCacheDemo {
public static void main(String[] args) throws ExecutionException {
LoadingCache<String, String> cache = CacheBuilder.newBuilder()
.maximumSize(100) // 最大緩存項數
.build(new CacheLoader<String, String>() {
@Override
public String load(String key) throws Exception {
return "Hello, " + key; // 定義緩存加載的方式
}
});
// 使用緩存
printCacheContents(cache);
System.out.println(cache.getUnchecked("hello")); // 輸出: HELLO
System.out.println(cache.getUnchecked("guava")); // 輸出: GUAVA
printCacheContents(cache);
}
public static void printCacheContents(LoadingCache<String, String> cache) {
Map<String, String> cacheMap = cache.asMap();
if (cacheMap.isEmpty()) {
System.out.println("現在緩存里還沒有內容====");
} else {
System.out.println("緩存內容如下內容====");
for (Map.Entry<String, String> entry : cacheMap.entrySet()) {
System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}
}
}
}
?運行內容如下
直接打印緩存內容
現在緩存里還沒有內容====
Hello, hello
Hello, guava
使用后打印緩存內容
緩存內容如下內容====
Key: hello, Value: Hello, hello
Key: guava, Value: Hello, guava
解釋:在這個例子里,創建了一個簡單的LoadingCache實例。通過
getUnchecked
方法獲取一個緩存項時,如果這個項不存在,CacheLoader會自動加載一個新值。在這里是簡單地返回一個字符串。
LoadingCache詳解
Google Guava 緩存工具使用詳解_google loadingcache-CSDN博客
CacheBuilder類
CacheBuilder類是用于創建Guava緩存的構建器。可以使用該類的newBuilder()方法創建一個構建器實例,并通過一系列方法設置緩存的屬性,例如最大容量、過期時間等。最后可以通過build()方法構建一個Cache實例。
部分方法詳解:
?
initialCapacity:設置緩存的初始容量
這個方法將通過一個整數值設置緩存的初始大小。它是一個可選的方法,如果沒有指定,緩存將采用默認的初始容量。
concurrencyLevel:設置并發級別
用于估計同時寫入的線程數。這個方法將通過一個整數值設置并發級別,用于內部數據結構的調整,以提高并發寫入的性能。它是一個可選的方法,缺省值為 4。
maximumSize:設置緩存的最大容量
這個方法將通過一個 long 類型的值設置緩存的最大容量。當緩存的條目數達到這個容量時,會觸發緩存清除策略來移除一些條目以騰出空間。它是一個可選的方法,如果沒有指定最大容量,緩存將不會有大小限制。
maximumWeight:設置緩存的最大權重
這個方法將通過一個 long 類型的值設置緩存的最大權重。權重可以根據緩存條目的大小計算,通常用于緩存對象大小不同的場景。當緩存的總權重達到這個值時,會觸發緩存清除策略來移除一些條目以騰出空間。它是一個可選的方法,如果沒有指定最大權重,緩存將不會有權重限制。
weigher:設置緩存的權重計算器
這個方法將通過一個實現了 Weigher 接口的對象設置緩存的權重計算器。通過權重計算器,可以根據緩存條目的鍵和值來計算它們的權重,以便在達到最大權重時觸發緩存清除策略。它是一個可選的方法,如果沒有設置權重計算器,緩存將不會有權重限制。
expireAfterWrite:設置寫入后過期時間
這個方法通過一個 java.time.Duration 對象設置緩存條目的寫入后過期時間。過期時間從最后一次寫入條目開始計算。一旦超過指定的時間,條目將被認為是過期的并被清除。這是一個可選的方法,如果沒有指定過期時間,條目將不會主動過期。
expireAfterAccess:設置訪問后過期時間
這個方法通過一個 java.time.Duration 對象設置緩存條目的訪問后過期時間。過期時間從最后一次訪問條目開始計算。一旦超過指定的時間,條目將被認為是過期的并被清除。這是一個可選的方法,如果沒有指定過期時間,條目將不會主動過期。
refreshAfterWrite:設置寫入后自動刷新時間
這個方法通過一個 java.time.Duration 對象設置緩存條目的自動刷新時間。自動刷新時間從最后一次寫入條目開始計算。一旦超過指定的時間,當條目被訪問時,緩存將自動刷新該條目,即會調用 CacheLoader 的 load 方法重新加載該條目。這是一個可選的方法,如果沒有設置自動刷新時間,條目將不會自動刷新。
recordStats():開啟緩存統計信息記錄
這個方法用于開啟緩存的統計信息記錄功能。一旦開啟,可以通過 Cache.stats() 方法獲取緩存的統計信息,如命中率、加載次數、平均加載時間等。這是一個可選的方法,如果不開啟統計信息記錄,將無法獲取緩存的統計信息。
注意事項:
maximumSize與maximumWeight不能同時設置
設置maximumWeight時必須設置weigher
當緩存失效后,refreshAfterWrite設置的寫入后自動刷新時間不會再有用
注意:expireAfterWrite、expireAfterAccess、refreshAfterWrite三個值的使用
開啟recordStats后,才進行統計
CacheLoader類
CacheLoader 可以被視為一種從存儲系統(如磁盤、數據庫或遠程節點)中加載數據的方法。CacheLoader 通常搭配refreshAfterWrite使用,在寫入指定的時間周期后會調用CacheLoader 的load方法來獲取并刷新為新值。
load 方法在以下情況下會被觸發調用:
- 當設置了refreshAfterWrite(寫入后自動刷新時間),達到自動刷新時間時,會調用reload 方法來重新加載該鍵的值,如果reload 方法違背重寫,reload 的默認實現會調用 load 方法來重新加載該鍵的值。
- 調用 Cache.get(key) 方法獲取緩存中指定鍵的值時,如果該鍵的值不存在,則會調用 load 方法來加載該鍵的值。
- 調用 Cache.get(key, callable) 方法獲取緩存中指定鍵的值時,如果該鍵的值存在,則直接返回;如果該鍵的值不存在,則會調用 callable 參數指定的回調函數來計算并加載該鍵的值。
- 調用 Cache.getUnchecked(key) 方法獲取緩存中指定鍵的值時,無論該鍵的值存在與否,都會調用 load 方法來加載該鍵的值。
需要注意的是:
當調用 load 方法加載緩存值時,可能會發生 IO 操作或其他耗時操作,因此建議在加載操作中使用異步方式來避免阻塞主線程。另外,加載操作的實現要考慮緩存的一致性和并發性,避免多個線程同時加載同一個鍵的值。
關于reload 方法:
reload 方法用于異步地刷新緩存值。它接收兩個參數:key 和 oldValue,分別表示需要刷新的鍵以及該鍵之前對應的舊值。實現者需要通過在 reload 方法體內通過新的數據源或其他方式來重新加載和構建緩存數據,并在加載完成之后返回新的緩存值即可。通過異步地來更新緩存數據,讓余下的請求可以同時從舊值中訪問數據。
相對于 load 方法,reload 方法多了一個參數 oldValue。這是因為 reload 方法在執行時,緩存項可能已經過期了,它需要使用舊值來保證其它線程可以繼續獲得前一緩存項的值,避免出現緩存穿透的情況。同時,reload 方法需要保證異步刷新緩存的情況下線程安全。
CacheStats類
CacheStats 對象提供了諸如緩存命中率、加載緩存項數、緩存項回收數等統計信息的訪問。
它可以通過 Cache.stats() 方法來獲取,從而方便開發者監控緩存狀態。
主要屬性:
RemovalListener類
RemovalListener 用于在緩存中某個值被移除時執行相應的回調操作。
可以使用 CacheBuilder.removalListener() 方法為緩存設置 RemovalListener。
RemovalListener 的使用:
創建一個實現 RemovalListener 接口的類,實現 onRemoval 方法。這個方法會在緩存項被移除時被調用,接受兩個參數: key 和 value。key 是被移除的緩存項的鍵,value 是被移除的緩存項的值。你可以根據需要在 onRemoval 方法中實現自定義的邏輯。
使用 CacheBuilder 的 removalListener 方法,將創建的 RemovalListener 對象傳遞給它。
緩存項被移除時,onRemoval 方法會自動被調用,方法會傳入一個RemovalNotification 類型的參數,里面包含相應的 key 和 value等信息。你可以在這個方法中執行自定義的業務邏輯,例如日志記錄、資源清理等操作。
RemovalNotification:
RemovalNotification 是 Guava 中用于表示緩存項被移除的通知的類。當在 Guava Cache 中注冊了 RemovalListener 后,RemovalNotification 對象會在緩存項被移除時傳遞給 RemovalListener 的 onRemoval 方法。
RemovalNotification 包含了有關被移除緩存項的一些重要信息,例如鍵、值以及移除原因。下面是 RemovalNotification 類中常用的屬性和方法:
getKey():獲取被移除的緩存項的鍵。
getValue():獲取被移除的緩存項的值。
getCause():獲取移除原因,它是一個枚舉類型RemovalCause,表示緩存項被移除的原因。
RemovalCause的可選值:
EXPLICIT:條目被顯式刪除,例如通過調用 Cache.invalidate(key) 方法。
REPLACED:條目被替換,例如通過調用 Cache.put(key, value) 方法重復放入相同的鍵。
EXPIRED:緩存條目由于達到了指定的過期時間而被移除。
SIZE:緩存條目由于超過了指定的大小限制而被移除。
COLLECTED:緩存條目被垃圾回收移除。這是在啟用了緩存值的弱引用或軟引用時發生的。
使用 RemovalNotification 可以讓你在緩存項被移除時獲取相關信息,并根據移除原因采取適當的處理措施。例如,你可以根據移除原因記錄日志、執行清理操作、發送通知等。這樣能夠增強緩存的功能和可觀察性。
注意事項:
RemovalListener 的 onRemoval 方法會在移除操作發生時同步調用,因此請確保不要在此方法中做耗時的操作,以免阻塞緩存的性能。
如果在緩存移除過程中拋出任何異常,它將被捕獲并記錄,不會影響緩存的正常運行。
需要注意的是,RemovalListener 只會在通過 Cache 的操作(如 invalidate、invalidateAll、put 進行替換)觸發移除時被調用,并不會在緩存項因為過期失效而自動移除時被調用。
使用 RemovalListener 可以方便地在緩存項被移除時執行一些自定義的操作,例如清理相關資源、更新其他緩存或發送通知等。根據實際需要,合理利用 RemovalListener 可以增強緩存的功能和靈活性。
實際使用
自動加載和刷新機制
對于LoadingCache,當請求某個鍵的值時,如果這個值不存在或者需要刷新,LoadingCache會自動調用CacheLoader
去加載或刷新數據。
public class AutoRefreshCache {
public static void main(String[] args) throws Exception {
LoadingCache<String, String> cache = CacheBuilder.newBuilder()
.refreshAfterWrite(1, TimeUnit.MINUTES) // 設置1分鐘后刷新
.build(new CacheLoader<String, String>() {
@Override
public String load(String key) {
return fetchDataFromDatabase(key); // 模擬從數據庫加載數據
}
});
System.out.println("First load: " + cache.get("key")); // 第一次加載
System.out.println("Waiting for 2 minute...");
Thread.sleep(TimeUnit.MINUTES.toMillis(2));
// 2分鐘后,嘗試再次獲取,將觸發刷新操作
System.out.println("Second load after refresh: " + cache.get("key"));
}
private static String fetchDataFromDatabase(String key) {
// 模擬數據庫操作
return System.currentTimeMillis() + key;
}
}
運行結果
First load: 1720669175847key
Waiting for 2 minute...
Second load after refresh: 1720669295853key
異常處理
public class ExceptionHandlingCache {
public static void main(String[] args) throws Exception {
LoadingCache<String, String> cache = CacheBuilder.newBuilder()
.build(new CacheLoader<String, String>() {
@Override
public String load(String key) throws Exception {
if (!checkKey(key)) {
throw new Exception("Loading error");
}
return "Data for " + key;
}
});
getByKey(cache,"key");
getByKey(cache,"errorKey");
}
public static String getByKey(LoadingCache<String, String> cache, String key) {
try {
String value = cache.get(key);
System.out.println("Value for key \"" + key + "\": " + value);
return value;
} catch (Exception e) {
System.out.println("Error during cache load for key \"" + key + "\": " + e.getMessage());
return null;
}
}
public static boolean checkKey(String key) {
if ("errorKey".equals(key)){
return false;
}
return true;
}
}
輸出結果
Value for key "key": Data for key
Error during cache load for key "errorKey": java.lang.Exception: Loading error
統計和監聽功能
LoadingCache還提供了緩存統計和監聽功能,這對于監控緩存性能和行為非常有用。
public class CacheMonitoring {
public static void main(String[] args) throws Exception {
// 創建Cache實例
LoadingCache<String, String> cache = CacheBuilder.newBuilder()
.initialCapacity(2) // 設置初始容量
.concurrencyLevel(4) // 設置并發級別
.maximumSize(5) // 設置最大容量
// .maximumWeight(1000) // 設置最大權重
// .weigher((Weigher<String, String>) (k, v) -> v.length()) // 設置權重計算器
.expireAfterWrite(10, TimeUnit.SECONDS) // 寫入后x秒過期
.expireAfterAccess(20, TimeUnit.SECONDS) // 訪問x秒過期
.refreshAfterWrite(5, TimeUnit.SECONDS) // 寫入后自動刷新,
.recordStats() // 開啟統計信息記錄
.removalListener(notification -> { // 設置移除監聽
// 緩存Key被移除時觸發
String cause = "";
if (RemovalCause.EXPLICIT.equals(notification.getCause())) {
cause = "被顯式移除";
} else if (RemovalCause.REPLACED.equals(notification.getCause())) {
cause = "被替換";
} else if (RemovalCause.EXPIRED.equals(notification.getCause())) {
cause = "被過期移除";
} else if (RemovalCause.SIZE.equals(notification.getCause())) {
cause = "被緩存條數超上限移除";
} else if (RemovalCause.COLLECTED.equals(notification.getCause())) {
cause = "被垃圾回收移除";
}
System.out.println(getCurrentFormattedTime() + " Key: " + notification.getKey() + " 移除了, 移除原因: " + cause);
})
.build(new CacheLoader<String, String>() { // 設置緩存重新加載邏輯
@Override
public String load(String key) {
// 重新加載指定Key的值
String newValue = "value" + (int)Math.random()*100;
System.out.println(getCurrentFormattedTime() + " Key: " + key + " 重新加載,新value:" + newValue);
return newValue;
}
});
// 將數據放入緩存
cache.put("key0", "value0");
cache.invalidate("key0");
cache.put("key1", "value1");
cache.put("key1", "value11");
cache.put("key2", "value22");
cache.put("key3", "value3");
cache.put("key4", "value4");
cache.put("key5", "value5");
cache.put("key6", "value6");
cache.put("key7", "value7");
cache.put("key8", "value8");
while (true) {
// 獲取數據
System.out.println(getCurrentFormattedTime() + " get key1 value: " + cache.get("key1"));
// 統計信息
System.out.println(getCurrentFormattedTime() + " get stats: " + cache.stats());
Thread.sleep(3000);
}
}
public static String getCurrentFormattedTime() {
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
return now.format(formatter);
}
}
打印日志如下
2024-07-11 12:09:01 Key: key0 移除了, 移除原因: 被顯式移除
2024-07-11 12:09:01 Key: key1 移除了, 移除原因: 被替換
2024-07-11 12:09:01 Key: key1 移除了, 移除原因: 被緩存條數超上限移除
2024-07-11 12:09:01 Key: key2 移除了, 移除原因: 被緩存條數超上限移除
2024-07-11 12:09:01 Key: key3 移除了, 移除原因: 被緩存條數超上限移除
2024-07-11 12:09:01 Key: key1 重新加載,新value:value0
2024-07-11 12:09:01 Key: key4 移除了, 移除原因: 被緩存條數超上限移除
2024-07-11 12:09:01 get key1 value: value0
2024-07-11 12:09:01 get stats: CacheStats{hitCount=0, missCount=1, loadSuccessCount=1, loadExceptionCount=0, totalLoadTime=951083, evictionCount=4}
2024-07-11 12:09:04 get key1 value: value0
2024-07-11 12:09:04 get stats: CacheStats{hitCount=1, missCount=1, loadSuccessCount=1, loadExceptionCount=0, totalLoadTime=951083, evictionCount=4}
2024-07-11 12:09:07 Key: key1 重新加載,新value:value0
2024-07-11 12:09:07 Key: key1 移除了, 移除原因: 被替換
原文鏈接:https://blog.csdn.net/weixin_43851023/article/details/140346730
- 上一篇:沒有了
- 下一篇:沒有了
相關推薦
- 2023-03-20 c#判斷代碼是否執行超時的幾種方式總結_C#教程
- 2022-03-20 C語言數學公式來實現土味表白_C 語言
- 2022-11-20 WPF實現自帶觸控鍵盤的文本框_C#教程
- 2022-04-30 C#操作DataGridView獲取或設置當前單元格的內容_C#教程
- 2022-08-14 hyper-v如何配置NAT網絡的實現_Hyper-V
- 2022-09-20 Redis深入了解內存淘汰與事務操作_Redis
- 2022-03-29 python自動化之re模塊詳解_python
- 2023-06-18 C#?System.TypeInitializationException?異常處理方案_C#教程
- 欄目分類
-
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支