Java設(shè)置Map過(guò)期時(shí)間的的幾種方法舉例詳解
一、技術(shù)背景
在實(shí)際的項(xiàng)目開(kāi)發(fā)中,我們經(jīng)常會(huì)使用到緩存中間件(如redis、MemCache等)來(lái)幫助我們提高系統(tǒng)的可用性和健壯性。
但是很多時(shí)候如果項(xiàng)目比較簡(jiǎn)單,就沒(méi)有必要為了使用緩存而專門引入Redis等等中間件來(lái)加重系統(tǒng)的復(fù)雜性。那么使用Java本身自己的輕量級(jí)的緩存組件就是完美解決方式。
二、技術(shù)效果
- 實(shí)現(xiàn)緩存的常見(jiàn)功能
- 熱點(diǎn)數(shù)據(jù)預(yù)熱
- 簡(jiǎn)單限流
- 去重
三、ExpiringMap
3.1 ExpiringMap簡(jiǎn)介
ExpiringMap具有高性能、低開(kāi)銷、零依賴、線程安全、使用 ConcurrentMap 的實(shí)現(xiàn)過(guò)期 entries 等優(yōu)點(diǎn)。
功能包括不限于:
- 設(shè)置 Map 中的 Entry 在一段時(shí)間后自動(dòng)過(guò)期。
- 設(shè)置 Map 最大容納值,當(dāng)?shù)竭_(dá) Max size 后,再次插入值會(huì)導(dǎo)致 Map 中的第一個(gè)值過(guò)期。
- 設(shè)置 添加監(jiān)聽(tīng)事件,在監(jiān)聽(tīng)到 Entry 過(guò)期時(shí)調(diào)度監(jiān)聽(tīng)函數(shù)。
- 設(shè)置懶加載,在調(diào)用 get() 方法時(shí)創(chuàng)建對(duì)象。
- 允許您了解條目預(yù)計(jì)何時(shí)過(guò)期
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存儲(chǔ)的最大值,類似隊(duì)列,容量固定,當(dāng)操作map容量超出限制時(shí),最開(kāi)始的元素就會(huì)依次過(guò)期,只保留最新的; * ② expiration:過(guò)期時(shí)間; * ③ expirationListener:過(guò)期監(jiān)聽(tīng),當(dāng)條目過(guò)期時(shí),將同步調(diào)用過(guò)期偵聽(tīng)器,并且在偵聽(tīng)器完成之前, * 將阻止對(duì)映射的寫入操作。還可以在單獨(dú)的線程池中配置和調(diào)用異步過(guò)期偵聽(tīng)器,而不會(huì)阻塞映射操作; * ④ expirationPolicy:過(guò)期策略,包括 ExpirationPolicy.ACCESSED 和 ExpirationPolicy.CREATED 兩種; * 1)ExpirationPolicy.ACCESSED :每進(jìn)行一次訪問(wèn),過(guò)期時(shí)間就會(huì)自動(dòng)清零,重新計(jì)算; * 2)ExpirationPolicy.CREATED:在過(guò)期時(shí)間內(nèi)重新 put 值的話,過(guò)期時(shí)間會(huì)清理,重新計(jì)算; * ⑤ variableExpiration:可變過(guò)期,條目可以具有單獨(dú)可變的到期時(shí)間和策略: */ 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已過(guò)期,key:"+ key); }) .build();
3.2.3 參數(shù)說(shuō)明
① maxSize:Map存儲(chǔ)的最大值,類似隊(duì)列,容量固定,當(dāng)操作map容量超出限制時(shí),最開(kāi)始的元素就會(huì)依次過(guò)期,只保留最新的; ② expiration:過(guò)期時(shí)間; ③ expirationListener:過(guò)期監(jiān)聽(tīng),當(dāng)條目過(guò)期時(shí),將同步調(diào)用過(guò)期偵聽(tīng)器,并且在偵聽(tīng)器完成之前, 將阻止對(duì)映射的寫入操作。還可以在單獨(dú)的線程池中配置和調(diào)用異步過(guò)期偵聽(tīng)器,而不會(huì)阻塞映射操作; ④ expirationPolicy:過(guò)期策略,包括 ExpirationPolicy.ACCESSED 和 ExpirationPolicy.CREATED 兩種; 1)ExpirationPolicy.ACCESSED :每進(jìn)行一次訪問(wèn),過(guò)期時(shí)間就會(huì)自動(dòng)清零,重新計(jì)算; 2)ExpirationPolicy.CREATED:在過(guò)期時(shí)間內(nèi)重新 put 值的話,過(guò)期時(shí)間會(huì)清理,重新計(jì)算; ⑤ variableExpiration:可變過(guò)期,條目可以具有單獨(dú)可變的到期時(shí)間和策略;
3.2.4 其他使用方式
//為單個(gè)條目指定到期策略: map.put("1", "張三", ExpirationPolicy.CREATED); map.put("2", "李四", ExpirationPolicy.ACCESSED); //variableExpiration 可變過(guò)期 條目可以具有單獨(dú)可變的到期時(shí)間和策略: map.put("3", "王五", ExpirationPolicy.ACCESSED, 5, TimeUnit.MINUTES); //過(guò)期時(shí)間和策略也可以即時(shí)更改: map.setExpiration("1", 5, TimeUnit.MINUTES); map.setExpirationPolicy("1", ExpirationPolicy.ACCESSED); //動(dòng)態(tài)添加和刪除過(guò)期偵聽(tīng)器: ExpirationListener<String, String> connectionCloser = (key, value) -> System.out.println(key+":"+value); //添加偵聽(tīng)器 map.addExpirationListener(connectionCloser); //移除偵聽(tīng)器 map.removeExpirationListener(connectionCloser); //設(shè)置懶加載 // Map<String, String> stringMap = ExpiringMap.builder() // .expiration(10, TimeUnit.MINUTES) // .entryLoader(address -> address) // .build(); // // 通過(guò) EntryLoader 將值加載到map中 // String value = stringMap.get("1"); // System.out.println("value值:"+value); //獲取條目的到期時(shí)間:?jiǎn)挝?毫秒 long expiration = map.getExpectedExpiration("1"); System.out.println("距離過(guò)期時(shí)間還有:"+expiration+"毫秒"); //重置條目的內(nèi)部到期計(jì)時(shí)器: map.resetExpiration("1"); //查看設(shè)置的過(guò)期時(shí)間 map.getExpiration("1"); System.out.println("設(shè)置的過(guò)期時(shí)間:"+map.getExpiration("1"));
測(cè)試結(jié)果
距離過(guò)期時(shí)間還有:299999毫秒
設(shè)置的過(guò)期時(shí)間:300000
四、Guava的LoadingCache
4.1 LoadingCache簡(jiǎn)介
做java的我們都知道Guava是一個(gè)編程工具類庫(kù),其中包含了很多高質(zhì)量高性能的工具類和方法。其中,LoadingCache便是一個(gè)特別好用的功能,其背后的架構(gòu)其實(shí)就是Guava cache,Guava Cache 是一個(gè)全內(nèi)存的本地緩存實(shí)現(xiàn),它提供了線程安全的實(shí)現(xiàn)機(jī)制,它可以加載緩存中不存在的數(shù)據(jù),本質(zhì)其實(shí)是一個(gè)鍵值對(duì)(key-value)的緩存,可以通過(guò)key獲取到對(duì)應(yīng)的緩存值value。
特點(diǎn):提供緩存回收機(jī)制,監(jiān)控緩存加載/命中情況,靈活強(qiáng)大的功能,簡(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ù)量到達(dá)該大小時(shí), 開(kāi)始回收舊的數(shù)據(jù) .maximumSize(1000) // 設(shè)置時(shí)間60s對(duì)象沒(méi)有被讀/寫訪問(wèn)則對(duì)象從內(nèi)存中刪除 .expireAfterAccess(60, TimeUnit.SECONDS) // 設(shè)置緩存在寫入之后 設(shè)定時(shí)間60s后失效 .expireAfterWrite(60, TimeUnit.SECONDS) // 定時(shí)刷新,設(shè)置時(shí)間10s后,當(dāng)有訪問(wèn)時(shí)會(huì)重新執(zhí)行l(wèi)oad方法重新加載 .refreshAfterWrite(10, TimeUnit.SECONDS) // 移除監(jiān)聽(tīng)器,緩存項(xiàng)被移除時(shí)會(huì)觸發(fā) .removalListener(new RemovalListener() { @Override public void onRemoval(RemovalNotification rn) { // 處理緩存鍵不存在緩存值時(shí)的處理邏輯 log.error(rn.getKey() + "remove"); } }) // 處理緩存鍵對(duì)應(yīng)的緩存值不存在時(shí)的處理邏輯 .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ù)說(shuō)明
① maximumSize:緩存的k-v最大數(shù)據(jù),當(dāng)總緩存的數(shù)據(jù)量達(dá)到這個(gè)值時(shí),就會(huì)淘汰它認(rèn)為不太用的一份數(shù)據(jù),會(huì)使用LRU策略進(jìn)行回收; ② expireAfterAccess:緩存項(xiàng)在給定時(shí)間內(nèi)沒(méi)有被讀/寫訪問(wèn),則回收,這個(gè)策略主要是為了淘汰長(zhǎng)時(shí)間不被訪問(wèn)的數(shù)據(jù); ③ expireAfterWrite:緩存項(xiàng)在給定時(shí)間內(nèi)沒(méi)有被寫訪問(wèn)(創(chuàng)建或覆蓋),則回收, 防止舊數(shù)據(jù)被緩存過(guò)久; ④ refreshAfterWrite:緩存項(xiàng)在給定時(shí)間內(nèi)沒(méi)有被寫訪問(wèn)(創(chuàng)建或覆蓋),則刷新; ⑤ recordStats:開(kāi)啟Cache的狀態(tài)統(tǒng)計(jì)(默認(rèn)是開(kāi)啟的);
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) { // 記錄該值的最近訪問(wèn)時(shí)間 recordRead(e, now); statsCounter.recordHits(1); // 內(nèi)部實(shí)現(xiàn)了定時(shí)刷新,若未開(kāi)啟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); } } } // 如果沒(méi)拿到有效的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 移除機(jī)制
guava做cache時(shí)候數(shù)據(jù)的移除分為被動(dòng)移除和主動(dòng)移除兩種。
- 被動(dòng)移除
- 基于大小的移除:數(shù)量達(dá)到指定大小,會(huì)把不常用的鍵值移除
- 基于時(shí)間的移除:expireAfterAccess(long, TimeUnit) 根據(jù)某個(gè)鍵值對(duì)最后一次訪問(wèn)之后多少時(shí)間后移除。expireAfterWrite(long, TimeUnit) 根據(jù)某個(gè)鍵值對(duì)被創(chuàng)建或值被替換后多少時(shí)間移除
- 基于引用的移除:主要是基于java的垃圾回收機(jī)制,根據(jù)鍵或者值的引用關(guān)系決定移除
- 主動(dòng)移除
- 單獨(dú)移除:Cache.invalidate(key)
- 批量移除:Cache.invalidateAll(keys)
- 移除所有:Cache.invalidateAll()
如果配置了移除監(jiān)聽(tīng)器RemovalListener,則在所有移除的動(dòng)作時(shí)會(huì)同步執(zhí)行該listener下的邏輯。
如需改成異步,使用:RemovalListeners.asynchronous(RemovalListener, Executor)。
4.4 其他
- 在put操作之前,如果已經(jīng)有該鍵值,會(huì)先觸發(fā)removalListener移除監(jiān)聽(tīng)器,再添加
- 配置了expireAfterAccess和expireAfterWrite,但在指定時(shí)間后沒(méi)有被移除。
- 刪除策略邏輯:
CacheBuilder構(gòu)建的緩存不會(huì)在特定時(shí)間自動(dòng)執(zhí)行清理和回收工作,也不會(huì)在某個(gè)緩存項(xiàng)過(guò)期后馬上清理,它不會(huì)啟動(dòng)一個(gè)線程來(lái)進(jìn)行緩存維護(hù),因?yàn)槭紫染€程相對(duì)較重,其次某些環(huán)境限制線程的創(chuàng)建。
它會(huì)在寫操作時(shí)順帶做少量的維護(hù)工作,或者偶爾在讀操作時(shí)做。當(dāng)然,也可以創(chuàng)建自己的維護(hù)線程,以固定的時(shí)間間隔調(diào)用Cache.cleanUp()。
總結(jié)
到此這篇關(guān)于Java設(shè)置Map過(guò)期時(shí)間的的幾種方法的文章就介紹到這了,更多相關(guān)Java設(shè)置Map過(guò)期時(shí)間內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java 中校驗(yàn)時(shí)間格式的常見(jiàn)方法
在實(shí)際項(xiàng)目開(kāi)發(fā)中,跟時(shí)間參數(shù)打交道是必不可少的,為了保證程序的安全性、健壯性,一般都會(huì)對(duì)參數(shù)進(jìn)行校驗(yàn),其他類型的參數(shù)校驗(yàn)很好實(shí)現(xiàn),那你知道時(shí)間參數(shù)的是怎么校驗(yàn)的嗎,下面給大家分享Java 中校驗(yàn)時(shí)間格式的方法,感興趣的朋友跟隨小編一起看看吧2024-08-08SpringBoot使用swagger生成api接口文檔的方法詳解
在之前的文章中,使用mybatis-plus生成了對(duì)應(yīng)的包,在此基礎(chǔ)上,我們針對(duì)項(xiàng)目的api接口,添加swagger配置和注解,生成swagger接口文檔,需要的可以了解一下2022-10-10Java面向?qū)ο笾甪inal關(guān)鍵字詳細(xì)解讀
這篇文章主要介紹了Java面向?qū)ο笾甪inal關(guān)鍵字詳細(xì)解讀,final修飾的屬性又叫常量,一般用 XX_XX_XX來(lái)命名,final修飾的屬性在定義時(shí)必須賦初始值,并且以后不能再修改,需要的朋友可以參考下2024-01-01利用Java正則表達(dá)式校驗(yàn)郵箱與手機(jī)號(hào)
利用Java正則表達(dá)式校驗(yàn)郵箱與手機(jī)號(hào)。需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2013-10-10使用SpringBoot中web項(xiàng)目推薦目錄結(jié)構(gòu)的問(wèn)題
這篇文章主要介紹了SpringBoot中web項(xiàng)目推薦目錄結(jié)構(gòu)的問(wèn)題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-01-01Mybatis 中的sql批量修改方法實(shí)現(xiàn)
在項(xiàng)目中遇到需要批量更新的功能,原本想的是在Java中用循環(huán)訪問(wèn)數(shù)據(jù)庫(kù)去更新,但是心里總覺(jué)得這樣做會(huì)不會(huì)太頻繁了,太耗費(fèi)資源了,效率也很低,查了下mybatis的批量操作,原來(lái)確實(shí)有<foreach>標(biāo)簽可以做到,下面通過(guò)本文給大家介紹下2017-01-01Spring Boot構(gòu)建優(yōu)雅的RESTful接口過(guò)程詳解
這篇文章主要介紹了spring boot構(gòu)建優(yōu)雅的RESTful接口過(guò)程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-08-08packages思維及使用Java添加Android平臺(tái)特定實(shí)現(xiàn)
這篇文章主要為大家介紹了packages思維及使用Java添加Android平臺(tái)特定實(shí)現(xiàn)在Flutter框架里的體現(xiàn)和運(yùn)用詳解,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12