在日常開發中有很多這樣的場景:有一些業務系統的配置信息,數據量不大,修改頻率不高,但是訪問很頻繁。如果每次程序都從數據庫或集中式緩存中獲取,受限于硬盤 I/O性能、遠程網絡訪問限制等,程序的執行效率不高。在這樣的業務場景中,我們可以通過本地緩存來提升數據訪問的效率。
今天我們來基于ConcurrentHashMap與ScheduledThreadPoolExecutor來實現一個線程安全的本地緩存:LocalCache。在LocalCache中支持永久緩存與臨時緩存,永久緩存的數據一直有效,臨時緩存的數據在指定時間到期之后會自動從緩存中移出。
LocalCache提供了數據安全的增、刪、改、查功能,具體方法如下所示:
方法名稱 | 方法說明 |
put(String key , V value) | 向緩存中插入數據,數據永久有效 |
put(String key , V value , int seconds) | 向緩存中插入數據,數據根據設定的時間生效,時間到期會從緩存中移出 |
containKey(String key) | 判斷緩存中是否包含對應的key |
get(String key) | 根據key從緩存中獲取數據 |
remove(String key) | 移出緩存中對應key的數據 |
shutdownNow() | 關閉緩存池 |
LocalCache主要由3個部分組成:數據緩存、數據超時時間、數據清理任務。數據緩存和數據超時時間都采用ConcurrentHashMap來存儲數據,數據超時時間中Key為數據存儲的鍵,value是數據的時間戳。數據清理任務采用ScheduledThreadPoolExecutor實現任務調度,默認的任務線程數為1,這樣可以避免多線程帶來的并發修改問題,同時線程都是內存操作,這樣單線程同樣具備高性能。
本地緩存的設計如下圖所示:
圖片
每次項緩存中插入數據時,LocalCache首先會將數據插入到ConcurrentHashMap中。然后判斷有沒有設置超時時間,如果有超時時間,LocalCache會將失效時間插入到ConcurrentHashMap中,并創建數據清理任務,之后任務提交到ScheduledThreadPoolExecutor線程池中。
每次從緩存中查詢數據,LocalCache會直接從ConcurrentHashMap中讀取數據。
定時任務線程池會按照超時時間來觸發數據清理任務,數據清理任務會從數據時長的緩存池中獲取Key對應的時間,判斷當前Key對應的數據是否已經到期了。如果數據已經到期了,LocalCache會調用remove方法將數據從緩存池中移除。
LocalCache作為本地緩存的接口,定義了數據插入、數據刪除、數據查詢的相關接口方法。DefaultLocalCache 定義了兩個ConcurrentHashMap變量:dataMap和timeOutMap。dataMap用來緩存數據信息,timeOutMap用來存儲數據失效的時間戳,同時還定義了數據清理任務ClearTask,ClearTask負責將過期的數據從dataMap中移除。UML圖如下所示:
圖片
public interface LocalCache<V> { /** * 插入數據,數據永久有效 */ boolean put(String key, V value); /** * 插入數據,在指定時間內生效 */ boolean put(String key, V value, int seconds); /** * 是否包含指定的key */ boolean containKey(String key); /** * 獲取指定Key的值 */ V get(String key); /** * 從緩存中移除key對應的數據 */ void remove(String key); void shutdownNow();}
在接口LocalCache中定義了兩個數據插入的put接口:一個沒有到期時間,另一個有到期時間。沒有到期時間表示數據永久有效,有到期時間的數據會在到期后從緩存中移除。
在接口實現DefaultLocalCache內部定義了三個常量:緩存的默認大小DEFAULT_CAPACITY、最大容量MAX_CAPACITY、定時線程池的大小DEFAULT_THREAD_SIZE。核心代碼如下:
public class DefaultLocalCache<V> implements LocalCache<V> { // 默認容量 private static final int DEFAULT_CAPACITY = 1024; private static final int MAX_CAPACITY = 100000; private static final int DEFAULT_THREAD_SIZE = 1; private final int maxSize; //數據map private volatile ConcurrentHashMap<String,V> dataMap; //過期時間 private final ConcurrentHashMap<String,Long> timeOutMap; //定時任務 private final ScheduledExecutorService executorService; public DefaultLocalCache() { maxSize = MAX_CAPACITY; dataMap = new ConcurrentHashMap<>(DEFAULT_CAPACITY); timeOutMap = new ConcurrentHashMap<>(DEFAULT_CAPACITY); executorService = new ScheduledThreadPoolExecutor(DEFAULT_THREAD_SIZE) ; } public DefaultLocalCache(int size) { maxSize = size; dataMap = new ConcurrentHashMap<>(DEFAULT_CAPACITY); timeOutMap = new ConcurrentHashMap<>(DEFAULT_CAPACITY); executorService = new ScheduledThreadPoolExecutor(DEFAULT_THREAD_SIZE) ; } @Override public boolean put(String key, V value) { //檢查容量 if(checkCapacity()){ dataMap.put(key,value); return true; } return false; } @Override public boolean put(String key, V value, int seconds) { if(checkCapacity()){ dataMap.put(key,value); if(seconds >= 0){ timeOutMap.put(key,getTimeOut(seconds)); ClearTask task = new ClearTask(key); executorService.schedule(task, seconds, TimeUnit.SECONDS); } } return false; } ...... class ClearTask implements Runnable{ private String key; public ClearTask(String key){ this.key = key; } @Override public void run() { //判斷緩存中是否有key if(timeOutMap.contains(key)){ //獲取失效時間 Long expire = timeOutMap.get(key); //如果失效時間大于0,并且比當前時間小,則刪除緩存 if(expire > 0){ long now = System.currentTimeMillis(); if(now >= expire){ remove(key); } } } } }}
在LocalCache的默認實現DefaultLocalCache中,基于ConcurrentHashMap與ScheduledThreadPoolExecutor結合使用,使得LocalCache支持永久緩存與臨時緩存兩種能力。
本文鏈接:http://www.tebozhan.com/showinfo-26-82181-0.html面試官:如何設計和實現一個帶過期時間的本地緩存?
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
上一篇: JVM類加載:如何手寫自定義類加載器,命名空間詳解
下一篇: 四萬字102道Java多線程經典面試題