Java設(shè)置Map過期時間的的幾種方法舉例詳解
一、技術(shù)背景
在實際的項目開發(fā)中,我們經(jīng)常會使用到緩存中間件(如redis、MemCache等)來幫助我們提高系統(tǒng)的可用性和健壯性。
但是很多時候如果項目比較簡單,就沒有必要為了使用緩存而專門引入Redis等等中間件來加重系統(tǒng)的復(fù)雜性。那么使用Java本身自己的輕量級的緩存組件就是完美解決方式。
二、技術(shù)效果
- 實現(xiàn)緩存的常見功能
- 熱點數(shù)據(jù)預(yù)熱
- 簡單限流
- 去重
三、ExpiringMap
3.1 ExpiringMap簡介
ExpiringMap具有高性能、低開銷、零依賴、線程安全、使用 ConcurrentMap 的實現(xiàn)過期 entries 等優(yōu)點。
功能包括不限于:
- 設(shè)置 Map 中的 Entry 在一段時間后自動過期。
- 設(shè)置 Map 最大容納值,當(dāng)?shù)竭_ Max size 后,再次插入值會導(dǎo)致 Map 中的第一個值過期。
- 設(shè)置 添加監(jiān)聽事件,在監(jiān)聽到 Entry 過期時調(diào)度監(jiān)聽函數(shù)。
- 設(shè)置懶加載,在調(diào)用 get() 方法時創(chuàng)建對象。
- 允許您了解條目預(yù)計何時過期
3.2 ExpiringMap使用
3.2.1 pom.xml 中添加依賴
<!-- https://mvnrepository.com/artifact/net.jodah/expiringmap -->
<dependency>
<groupId>net.jodah</groupId>
<artifactId>expiringmap</artifactId>
<version>0.5.10</version>
</dependency>
3.2.2 代碼中使用
/**
* ① maxSize:Map存儲的最大值,類似隊列,容量固定,當(dāng)操作map容量超出限制時,最開始的元素就會依次過期,只保留最新的;
* ② expiration:過期時間;
* ③ expirationListener:過期監(jiān)聽,當(dāng)條目過期時,將同步調(diào)用過期偵聽器,并且在偵聽器完成之前,
* 將阻止對映射的寫入操作。還可以在單獨的線程池中配置和調(diào)用異步過期偵聽器,而不會阻塞映射操作;
* ④ expirationPolicy:過期策略,包括 ExpirationPolicy.ACCESSED 和 ExpirationPolicy.CREATED 兩種;
* 1)ExpirationPolicy.ACCESSED :每進行一次訪問,過期時間就會自動清零,重新計算;
* 2)ExpirationPolicy.CREATED:在過期時間內(nèi)重新 put 值的話,過期時間會清理,重新計算;
* ⑤ variableExpiration:可變過期,條目可以具有單獨可變的到期時間和策略:
*/
public static ExpiringMap<String, String> map = ExpiringMap.builder()
.maxSize(1000)
.expiration(2, TimeUnit.HOURS)
.variableExpiration()
.expirationPolicy(ExpirationPolicy.ACCESSED)
.expirationListener((key, value) -> {
System.out.println("SseEmitter已過期,key:"+ key);
})
.build();
3.2.3 參數(shù)說明
① maxSize:Map存儲的最大值,類似隊列,容量固定,當(dāng)操作map容量超出限制時,最開始的元素就會依次過期,只保留最新的;
② expiration:過期時間;
③ expirationListener:過期監(jiān)聽,當(dāng)條目過期時,將同步調(diào)用過期偵聽器,并且在偵聽器完成之前,
將阻止對映射的寫入操作。還可以在單獨的線程池中配置和調(diào)用異步過期偵聽器,而不會阻塞映射操作;
④ expirationPolicy:過期策略,包括 ExpirationPolicy.ACCESSED 和 ExpirationPolicy.CREATED 兩種;
1)ExpirationPolicy.ACCESSED :每進行一次訪問,過期時間就會自動清零,重新計算;
2)ExpirationPolicy.CREATED:在過期時間內(nèi)重新 put 值的話,過期時間會清理,重新計算;
⑤ variableExpiration:可變過期,條目可以具有單獨可變的到期時間和策略;
3.2.4 其他使用方式
//為單個條目指定到期策略:
map.put("1", "張三", ExpirationPolicy.CREATED);
map.put("2", "李四", ExpirationPolicy.ACCESSED);
//variableExpiration 可變過期 條目可以具有單獨可變的到期時間和策略:
map.put("3", "王五", ExpirationPolicy.ACCESSED, 5, TimeUnit.MINUTES);
//過期時間和策略也可以即時更改:
map.setExpiration("1", 5, TimeUnit.MINUTES);
map.setExpirationPolicy("1", ExpirationPolicy.ACCESSED);
//動態(tài)添加和刪除過期偵聽器:
ExpirationListener<String, String> connectionCloser = (key, value) -> System.out.println(key+":"+value);
//添加偵聽器
map.addExpirationListener(connectionCloser);
//移除偵聽器
map.removeExpirationListener(connectionCloser);
//設(shè)置懶加載
// Map<String, String> stringMap = ExpiringMap.builder()
// .expiration(10, TimeUnit.MINUTES)
// .entryLoader(address -> address)
// .build();
// // 通過 EntryLoader 將值加載到map中
// String value = stringMap.get("1");
// System.out.println("value值:"+value);
//獲取條目的到期時間:單位:毫秒
long expiration = map.getExpectedExpiration("1");
System.out.println("距離過期時間還有:"+expiration+"毫秒");
//重置條目的內(nèi)部到期計時器:
map.resetExpiration("1");
//查看設(shè)置的過期時間
map.getExpiration("1");
System.out.println("設(shè)置的過期時間:"+map.getExpiration("1"));
測試結(jié)果
距離過期時間還有:299999毫秒
設(shè)置的過期時間:300000
四、Guava的LoadingCache
4.1 LoadingCache簡介
做java的我們都知道Guava是一個編程工具類庫,其中包含了很多高質(zhì)量高性能的工具類和方法。其中,LoadingCache便是一個特別好用的功能,其背后的架構(gòu)其實就是Guava cache,Guava Cache 是一個全內(nèi)存的本地緩存實現(xiàn),它提供了線程安全的實現(xiàn)機制,它可以加載緩存中不存在的數(shù)據(jù),本質(zhì)其實是一個鍵值對(key-value)的緩存,可以通過key獲取到對應(yīng)的緩存值value。
特點:提供緩存回收機制,監(jiān)控緩存加載/命中情況,靈活強大的功能,簡單易上手的api。
4.2 LoadingCache使用
4.2.1 pom.xml 中添加依賴
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>24.1-jre</version> </dependency>
4.2.2 代碼中使用
public static LoadingCache<Long, User> userCache= CacheBuilder.newBuilder()
// 緩存池大小,在緩存數(shù)量到達該大小時, 開始回收舊的數(shù)據(jù)
.maximumSize(1000)
// 設(shè)置時間60s對象沒有被讀/寫訪問則對象從內(nèi)存中刪除
.expireAfterAccess(60, TimeUnit.SECONDS)
// 設(shè)置緩存在寫入之后 設(shè)定時間60s后失效
.expireAfterWrite(60, TimeUnit.SECONDS)
// 定時刷新,設(shè)置時間10s后,當(dāng)有訪問時會重新執(zhí)行l(wèi)oad方法重新加載
.refreshAfterWrite(10, TimeUnit.SECONDS)
// 移除監(jiān)聽器,緩存項被移除時會觸發(fā)
.removalListener(new RemovalListener() {
@Override
public void onRemoval(RemovalNotification rn) {
// 處理緩存鍵不存在緩存值時的處理邏輯
log.error(rn.getKey() + "remove");
}
})
// 處理緩存鍵對應(yīng)的緩存值不存在時的處理邏輯
.build(new CacheLoader<Long, User>() {
@Override
public User load(Long id) {
return getById(id);
}
});
public User getUser(Long id) {
User user = userCache.get(id);
}
public ImmutableMap<Long, User > getAll(List<Long> ids) throws ExecutionException {
return cache.getAll(ids);
}
4.2.3 參數(shù)說明
① maximumSize:緩存的k-v最大數(shù)據(jù),當(dāng)總緩存的數(shù)據(jù)量達到這個值時,就會淘汰它認為不太用的一份數(shù)據(jù),會使用LRU策略進行回收; ② expireAfterAccess:緩存項在給定時間內(nèi)沒有被讀/寫訪問,則回收,這個策略主要是為了淘汰長時間不被訪問的數(shù)據(jù); ③ expireAfterWrite:緩存項在給定時間內(nèi)沒有被寫訪問(創(chuàng)建或覆蓋),則回收, 防止舊數(shù)據(jù)被緩存過久; ④ refreshAfterWrite:緩存項在給定時間內(nèi)沒有被寫訪問(創(chuàng)建或覆蓋),則刷新; ⑤ recordStats:開啟Cache的狀態(tài)統(tǒng)計(默認是開啟的);
4.2.4 GET方法
V get(K key, int hash, CacheLoader<? super K, V> loader) throws ExecutionException {
try {
if (count != 0) { // read-volatile
ReferenceEntry<K, V> e = getEntry(key, hash);
if (e != null) {
long now = map.ticker.read();
//檢查entry是否符合expireAfterAccess淘汰策略
V value = getLiveValue(e, now);
// value是有效的 則返回
if (value != null) {
// 記錄該值的最近訪問時間
recordRead(e, now);
statsCounter.recordHits(1);
// 內(nèi)部實現(xiàn)了定時刷新,若未開啟refreshAfterWrite則直接返回value
return scheduleRefresh(e, key, hash, value, now, loader);
}
ValueReference<K, V> valueReference = e.getValueReference();
// 如果有別的線程已經(jīng)在load value,則等到其他線程完成后再取結(jié)果
if (valueReference.isLoading()) {
return waitForLoadingValue(e, key, valueReference);
}
}
}
// 如果沒拿到有效的value,則執(zhí)行加載邏輯;
return lockedGetOrLoad(key, hash, loader);
} catch (ExecutionException ee) {
...
} finally {
postReadCleanup();
}
}
4.2.5 Load方法
@GwtCompatible(emulated = true)
public abstract class CacheLoader<K, V> {
public abstract V load(K key) throws Exception;
}

4.3 移除機制
guava做cache時候數(shù)據(jù)的移除分為被動移除和主動移除兩種。
- 被動移除
- 基于大小的移除:數(shù)量達到指定大小,會把不常用的鍵值移除
- 基于時間的移除:expireAfterAccess(long, TimeUnit) 根據(jù)某個鍵值對最后一次訪問之后多少時間后移除。expireAfterWrite(long, TimeUnit) 根據(jù)某個鍵值對被創(chuàng)建或值被替換后多少時間移除
- 基于引用的移除:主要是基于java的垃圾回收機制,根據(jù)鍵或者值的引用關(guān)系決定移除
- 主動移除
- 單獨移除:Cache.invalidate(key)
- 批量移除:Cache.invalidateAll(keys)
- 移除所有:Cache.invalidateAll()
如果配置了移除監(jiān)聽器RemovalListener,則在所有移除的動作時會同步執(zhí)行該listener下的邏輯。
如需改成異步,使用:RemovalListeners.asynchronous(RemovalListener, Executor)。
4.4 其他
- 在put操作之前,如果已經(jīng)有該鍵值,會先觸發(fā)removalListener移除監(jiān)聽器,再添加
- 配置了expireAfterAccess和expireAfterWrite,但在指定時間后沒有被移除。
- 刪除策略邏輯:
CacheBuilder構(gòu)建的緩存不會在特定時間自動執(zhí)行清理和回收工作,也不會在某個緩存項過期后馬上清理,它不會啟動一個線程來進行緩存維護,因為首先線程相對較重,其次某些環(huán)境限制線程的創(chuàng)建。
它會在寫操作時順帶做少量的維護工作,或者偶爾在讀操作時做。當(dāng)然,也可以創(chuàng)建自己的維護線程,以固定的時間間隔調(diào)用Cache.cleanUp()。
總結(jié)
到此這篇關(guān)于Java設(shè)置Map過期時間的的幾種方法的文章就介紹到這了,更多相關(guān)Java設(shè)置Map過期時間內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot使用swagger生成api接口文檔的方法詳解
在之前的文章中,使用mybatis-plus生成了對應(yīng)的包,在此基礎(chǔ)上,我們針對項目的api接口,添加swagger配置和注解,生成swagger接口文檔,需要的可以了解一下2022-10-10
Java面向?qū)ο笾甪inal關(guān)鍵字詳細解讀
這篇文章主要介紹了Java面向?qū)ο笾甪inal關(guān)鍵字詳細解讀,final修飾的屬性又叫常量,一般用 XX_XX_XX來命名,final修飾的屬性在定義時必須賦初始值,并且以后不能再修改,需要的朋友可以參考下2024-01-01
使用SpringBoot中web項目推薦目錄結(jié)構(gòu)的問題
這篇文章主要介紹了SpringBoot中web項目推薦目錄結(jié)構(gòu)的問題,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-01-01
Spring Boot構(gòu)建優(yōu)雅的RESTful接口過程詳解
這篇文章主要介紹了spring boot構(gòu)建優(yōu)雅的RESTful接口過程詳解,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-08-08
packages思維及使用Java添加Android平臺特定實現(xiàn)
這篇文章主要為大家介紹了packages思維及使用Java添加Android平臺特定實現(xiàn)在Flutter框架里的體現(xiàn)和運用詳解,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-12-12

