使用ConcurrentHashMap實現快取
說實話在各種第三方工具和快取機制的成熟方案太多了,使用Java原生的ConcurrentHashMap意義實在不大,但是有時候自己想偷懶就用個簡單的快取ConcurrentHashMap還是能用的上的,首先我們要了解ConcurrentHashMap是什麼?
ConcurrentHashMap的簡要總結:
1、public V get(Object key)不涉及到鎖,也就是說獲得物件時沒有使用鎖;
2、put、remove方法要使用鎖,但並不一定有鎖爭用,原因在於ConcurrentHashMap將快取的變數分到多個Segment,每個Segment上有一個鎖,只要多個執行緒訪問的不是一個Segment就沒有鎖爭用,就沒有堵塞,各執行緒用各自的鎖,ConcurrentHashMap預設情況下生成16個Segment,也就是允許16個執行緒併發的更新而儘量沒有鎖爭用;
3、Iterator物件的使用,不一定是和其它更新執行緒同步,獲得的物件可能是更新前的物件,ConcurrentHashMap允許一邊更新、一邊遍歷,也就是說在Iterator物件遍歷的時候,ConcurrentHashMap也可以進行remove,put操作,且遍歷的資料會隨著remove,put操作產出變化,所以希望遍歷到當前全部資料的話,要麼以ConcurrentHashMap變數為鎖進行同步(synchronized該變數),要麼使用CopiedIterator包裝iterator,使其拷貝當前集合的全部資料,但是這樣生成的iterator不可以進行remove操作。
Hashtable和ConcurrentHashMap的不同點:
1、Hashtable對get,put,remove都使用了同步操作,它的同步級別是正對Hashtable來進行同步的,也就是說如果有執行緒正在遍歷集合,其他的執行緒就暫時不能使用該集合了,這樣無疑就很容易對效能和吞吐量造成影響,從而形成單點。而ConcurrentHashMap則不同,它只對put,remove操作使用了同步操作,get操作並不影響,詳情請看以上第1,2點,當前ConcurrentHashMap這樣的做法對一些執行緒要求很嚴格的程式來說,還是有所欠缺的,對應這樣的程式來說,如果不考慮效能和吞吐量問題的話,個人覺得使用Hashtable還是比較合適的;
2、Hashtable在使用iterator遍歷的時候,如果其他執行緒,包括本執行緒對Hashtable進行了put,remove等更新操作的話,就會丟擲ConcurrentModificationException異常,但如果使用ConcurrentHashMap的話,就不用考慮這方面的問題了,詳情請看以上第3點;
我們用ConcurrentHashMap實現一個簡單的快取,功能有set、get和設定快取值的時間,並且能定時清除快取。
package cn.yan.study.utils.cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.concurrent.*;
/**
* Created
* User study
* Date 2018/6/26
* Time 20:32
*/
public class ConcurrentHashMapCacheUtils {
private static Logger LOGGER = LoggerFactory.getLogger(ConcurrentHashMapCacheUtils.class);
/**
* 快取最大個數
*/
private static final Integer CACHE_MAX_NUMBER = 100;
/**
* 當前快取個數
*/
private static Integer CURRENT_SIZE = 0;
/**
* 時間一分鐘
*/
static Long ONE_MINUTE = 1 * 60 * 1000L;
/**
* 快取物件
*/
private static final Map<String, CacheObj> CACHE_OBJECT_MAP = new ConcurrentHashMap<>();
/**
* 這個記錄了快取使用的最後一次的記錄,最近使用的在最前面
*/
private static final List<String> CACHE_USE_LOG_LIST = new LinkedList<>();
/**
* 清理過期快取是否在執行
*/
private static Boolean CLEAN_THREAD_IS_RUN = false;
/**
* 清理執行緒的
*/
private static ExecutorService executor = Executors.newSingleThreadExecutor();
/**
* 設定快取
*/
public static void setCache(String cacheKey, Object cacheValue, long cacheTime) {
Long ttlTime = null;
if (cacheTime <= 0L) {
if (cacheTime == -1L) {
ttlTime = -1L;
} else {
return;
}
}
checkSize();
saveCacheUseLog(cacheKey);
CURRENT_SIZE = CURRENT_SIZE + 1;
if (ttlTime == null) {
ttlTime = System.currentTimeMillis() + cacheTime;
}
CacheObj cacheObj = new CacheObj(cacheValue, ttlTime);
CACHE_OBJECT_MAP.put(cacheKey, cacheObj);
LOGGER.info("have set key :" + cacheKey);
}
/**
* 設定快取
*/
public static void setCache(String cacheKey, Object cacheValue) {
setCache(cacheKey, cacheValue, -1L);
}
/**
* 獲取快取
*/
public static Object getCache(String cacheKey) {
startCleanThread();
if (checkCache(cacheKey)) {
saveCacheUseLog(cacheKey);
return CACHE_OBJECT_MAP.get(cacheKey).getCacheValue();
}
return null;
}
public static boolean isExist(String cacheKey) {
return checkCache(cacheKey);
}
/**
* 刪除所有快取
*/
public static void clear() {
LOGGER.info("have clean all key !");
CACHE_OBJECT_MAP.clear();
CURRENT_SIZE = 0;
}
/**
* 刪除某個快取
*/
public static void deleteCache(String cacheKey) {
Object cacheValue = CACHE_OBJECT_MAP.remove(cacheKey);
if (cacheValue != null) {
LOGGER.info("have delete key :" + cacheKey);
CURRENT_SIZE = CURRENT_SIZE - 1;
}
}
/**
* 判斷快取在不在,過沒過期
*/
private static boolean checkCache(String cacheKey) {
CacheObj cacheObj = CACHE_OBJECT_MAP.get(cacheKey);
if (cacheObj == null) {
return false;
}
if (cacheObj.getTtlTime() == -1L) {
return true;
}
if (cacheObj.getTtlTime() < System.currentTimeMillis()) {
deleteCache(cacheKey);
return false;
}
return true;
}
/**
* 刪除最近最久未使用的快取
*/
private static void deleteLRU() {
LOGGER.info("delete Least recently used run!");
String cacheKey = CACHE_USE_LOG_LIST.remove(CACHE_USE_LOG_LIST.size() - 1);
deleteCache(cacheKey);
}
/**
* 刪除過期的快取
*/
static void deleteTimeOut() {
LOGGER.info("delete time out run!");
List<String> deleteKeyList = new LinkedList<>();
for(Map.Entry<String, CacheObj> entry : CACHE_OBJECT_MAP.entrySet()) {
if (entry.getValue().getTtlTime() < System.currentTimeMillis() && entry.getValue().getTtlTime() != -1L) {
deleteKeyList.add(entry.getKey());
}
}
for (String deleteKey : deleteKeyList) {
deleteCache(deleteKey);
}
LOGGER.info("delete cache count is :" + deleteKeyList.size());
}
/**
* 檢查大小
* 噹噹前大小如果已經達到最大大小
* 首先刪除過期快取,如果過期快取刪除過後還是達到最大快取數目
* 刪除最久未使用快取
*/
private static void checkSize() {
if (CURRENT_SIZE >= CACHE_MAX_NUMBER) {
deleteTimeOut();
}
if (CURRENT_SIZE >= CACHE_MAX_NUMBER) {
deleteLRU();
}
}
/**
* 儲存快取的使用記錄
*/
private static synchronized void saveCacheUseLog(String cacheKey) {
CACHE_USE_LOG_LIST.remove(cacheKey);
CACHE_USE_LOG_LIST.add(0,cacheKey);
}
/**
* 設定清理執行緒的執行狀態為正在執行
*/
static void setCleanThreadRun() {
CLEAN_THREAD_IS_RUN = true;
}
/**
* 開啟清理過期快取的執行緒
*/
private static void startCleanThread() {
if (!CLEAN_THREAD_IS_RUN) {
// new Thread(new CleanTimeOutThread()).run();
executor.submit(new CleanTimeOutThread());
}
}
public static void showUtilsInfo() {
System.out.println("clean time out cache is run :" + CLEAN_THREAD_IS_RUN);
System.out.println("cache max count is :" + CACHE_MAX_NUMBER);
System.out.println("cache current count is :" + CURRENT_SIZE);
System.out.println("cache object map is :" + CACHE_OBJECT_MAP.toString());
System.out.println("cache use log list is :" + CACHE_USE_LOG_LIST.toString());
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(2 * 1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
ConcurrentHashMapCacheUtils.setCache("my_cache_key_" + i, i, 60*1000);
}
for (int i = 0; i < 100; i++) {
if (i > 10) {
ConcurrentHashMapCacheUtils.getCache("test");
}
try {
Thread.sleep(2 * 1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
ConcurrentHashMapCacheUtils.showUtilsInfo();
}
}
}
class CacheObj {
/**
* 快取物件
*/
private Object CacheValue;
/**
* 快取過期時間
*/
private Long ttlTime;
CacheObj(Object cacheValue, Long ttlTime) {
CacheValue = cacheValue;
this.ttlTime = ttlTime;
}
Object getCacheValue() {
return CacheValue;
}
Long getTtlTime() {
return ttlTime;
}
@Override
public String toString() {
return "CacheObj{" +
"CacheValue=" + CacheValue +
", ttlTime=" + ttlTime +
'}';
}
}
/**
* 每一分鐘清理一次過期快取
*/
class CleanTimeOutThread implements Runnable{
@Override
public void run() {
ConcurrentHashMapCacheUtils.setCleanThreadRun();
while (true) {
System.out.println("clean thread run ");
ConcurrentHashMapCacheUtils.deleteTimeOut();
try {
Thread.sleep(ConcurrentHashMapCacheUtils.ONE_MINUTE);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
相關文章
- 使用RxJava實現快取RxJava快取
- SpringBoot中使用Redis實現快取Spring BootRedis快取
- WEB 應用快取解析以及使用 Redis 實現分散式快取Web快取Redis分散式
- 使用ThreadLocal來實現一個本地快取thread快取
- SpringBoot快取管理(二) 整合Redis快取實現Spring Boot快取Redis
- spring boot使用Jedis整合Redis實現快取(AOP)Spring BootRedis快取
- 快速入門:使用 .NET Aspire 元件實現快取元件快取
- 實現AVPlayer離線快取快取
- CefSharp自定義快取實現快取
- 快取 LRU 和 LFU 實現快取
- 探討下如何更好的使用快取 —— Redis快取的特殊用法以及與本地快取一起構建多級快取的實現快取Redis
- 使用Go實現健壯的記憶體型快取Go記憶體快取
- 乾貨,使用布隆過濾器實現高效快取!過濾器快取
- SpringBoot中實現兩級快取Spring Boot快取
- LRU cache快取簡單實現快取
- 【SpringBoot】結合Redis實現快取Spring BootRedis快取
- Vue專案全域性配置頁面快取,實現按需讀取快取Vue快取
- Laravel 實現二級快取 提高快取的命中率和細粒化快取 keyLaravel快取
- 手把手使用 PHP 實現 LRU 快取淘汰演算法PHP快取演算法
- 在Kubernetes上使用Spring Boot實現Hazelcast分散式快取 – PiotrSpring BootAST分散式快取
- MUI呼叫原生自定義方法實現計算快取與清空快取UI快取
- 利用localstorage實現本地訊息快取快取
- JavaScript隨機數實現防止快取JavaScript隨機快取
- Spring @cacheable註解實現的快取Spring快取
- MySQL與Redis實現二級快取MySqlRedis快取
- 15.SpringBoot整合Redis快取實現Spring BootRedis快取
- laravel利用Redis來實現網站快取讀取LaravelRedis網站快取
- jetcache快取使用快取
- SpringBoot使用快取Spring Boot快取
- ConcurrentHashMap 實現原理和原始碼分析HashMap原始碼
- 利用ConcurrentHashMap來實現一個ConcurrentHashSetHashMap
- JS 實現快取演算法(FIFO/LRU)JS快取演算法
- 實現vue 的keep-alive快取功能VueKeep-Alive快取
- 借用workbox實現離線快取應用快取
- Retrofit和OkHttp實現 Android網路快取HTTPAndroid快取
- [記]SAF 中快取服務的實現快取
- 高手如何處理快取:SpringBoot整合Redis實現快取處理(AOP技術)!快取Spring BootRedis
- 基於Canal+Kafka實現快取實時更新Kafka快取