自己实现简单Java缓存类
生活随笔
收集整理的這篇文章主要介紹了
自己实现简单Java缓存类
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
需求分析
項目中經常會遇到這種場景:一份數據需要在多處共享,有些數據還有時效性,過期自動失效。比如手機驗證碼,發送之后需要緩存起來,然后處于安全性考慮,一般還要設置有效期,到期自動失效。我們怎么實現這樣的功能呢?
解決方案
代碼
import java.util.HashMap; import java.util.Map; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit;/*** @Author: lixk* @Date: 2018/5/9 15:03* @Description: 簡單的內存緩存工具類*/ public class Cache {/*** 鍵值對集合*/private final static Map<String, Entity> map = new HashMap<>();/*** 定時器線程池,用于清除過期緩存*/private final static ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();/*** 添加緩存** @param key 鍵* @param data 值*/public synchronized static void put(String key, Object data) {Cache.put(key, data, 0);}/*** 添加緩存** @param key 鍵* @param data 值* @param expire 過期時間,單位:毫秒, 0表示無限長*/public synchronized static void put(String key, Object data, long expire) {//清除原鍵值對Cache.remove(key);//設置過期時間if (expire > 0) {Future future = executor.schedule(new Runnable() {@Overridepublic void run() {//過期后清除該鍵值對synchronized (Cache.class) {map.remove(key);}}}, expire, TimeUnit.MILLISECONDS);map.put(key, new Entity(data, future));} else {//不設置過期時間map.put(key, new Entity(data, null));}}/*** 讀取緩存** @param key 鍵* @return*/public synchronized static <T> T get(String key) {Entity entity = map.get(key);return entity == null ? null : (T) entity.value;}/*** 清除緩存** @param key 鍵* @return*/public synchronized static <T> T remove(String key) {//清除原緩存數據Entity entity = map.remove(key);if (entity == null) {return null;}//清除原鍵值對定時器if (entity.future != null) {entity.future.cancel(true);}return (T) entity.value;}/*** 查詢當前緩存的鍵值對數量** @return*/public synchronized static int size() {return map.size();}/*** 緩存實體類*/private static class Entity {/*** 鍵值對的value*/public Object value;/*** 定時器Future*/public Future future;public Entity(Object value, Future future) {this.value = value;this.future = future;}} }說明
本工具類主要采用HashMap+定時器線程池實現,map用于存儲鍵值對數據,map的value是Cache的內部類對象Entity,Entity包含value和該鍵值對的生命周期定時器Future。Cache類對外只提供了幾個同步方法:
| put(key, value) | 插入緩存數據 |
| put(key, value, expire) | 插入帶過期時間的緩存數據, expire: 過期時間,單位:毫秒 |
| get(key) | 獲取緩存數據 |
| remove(key) | 刪除緩存數據 |
| size() | 查詢當前緩存記錄數 |
當添加鍵值對數據的時候,首先會調用remove()方法,清除掉原來相同key的數據,并取消對應的定時清除任務,然后添加新數據到map中,并且,如果設置了有效時間,則添加對應的定時清除任務到定時器線程池。
測試
測試代碼如下:
import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger;/*** @Author: lixk* @Date: 2018/5/9 16:40* @Description: 緩存工具類測試*/ public class CacheTest {/*** 測試** @param args*/public static void main(String[] args) throws InterruptedException {String key = "id";//不設置過期時間System.out.println("***********不設置過期時間**********");Cache.put(key, 123);System.out.println("key:" + key + ", value:" + Cache.get(key));System.out.println("key:" + key + ", value:" + Cache.remove(key));System.out.println("key:" + key + ", value:" + Cache.get(key));//設置過期時間System.out.println("\n***********設置過期時間**********");Cache.put(key, "123456", 1000);System.out.println("key:" + key + ", value:" + Cache.get(key));Thread.sleep(2000);System.out.println("key:" + key + ", value:" + Cache.get(key));System.out.println("\n***********100w讀寫性能測試************");//創建有10個線程的線程池,將1000000次操作分10次添加到線程池int threads = 10;ExecutorService pool = Executors.newFixedThreadPool(threads);//每批操作數量int batchSize = 100000;//添加{CountDownLatch latch = new CountDownLatch(threads);AtomicInteger n = new AtomicInteger(0);long start = System.currentTimeMillis();for (int t = 0; t < threads; t++) {pool.submit(() -> {for (int i = 0; i < batchSize; i++) {int value = n.incrementAndGet();Cache.put(key + value, value, 300000);}latch.countDown();});}//等待全部線程執行完成,打印執行時間latch.await();System.out.printf("添加耗時:%dms\n", System.currentTimeMillis() - start);}//查詢{CountDownLatch latch = new CountDownLatch(threads);AtomicInteger n = new AtomicInteger(0);long start = System.currentTimeMillis();for (int t = 0; t < threads; t++) {pool.submit(() -> {for (int i = 0; i < batchSize; i++) {int value = n.incrementAndGet();Cache.get(key + value);}latch.countDown();});}//等待全部線程執行完成,打印執行時間latch.await();System.out.printf("查詢耗時:%dms\n", System.currentTimeMillis() - start);}System.out.println("當前緩存容量:" + Cache.size());} }測試結果
***********不設置過期時間********** key:id, value:123 key:id, value:123 key:id, value:null***********設置過期時間********** key:id, value:123456 key:id, value:null***********100w讀寫性能測試************ 添加耗時:1729ms 查詢耗時:283ms 當前緩存容量:1000000測試程序使用有10個線程的線程池來模擬并發,分別執行一百萬次添加和查詢操作,時間大約在兩秒左右,表現還不錯,每秒近百萬讀寫應該還是可以滿足大多數高并發場景的 ^^
備注:如果對緩存失效延遲非常敏感,不能容忍百萬數據亞秒級失效延遲,必須保證嚴格失效時間的話,可以參考另一版實現(數據實體加入了過期時間,每次取出數據時會先做判斷)。地址:https://github.com/lixk/ECache
總結
以上是生活随笔為你收集整理的自己实现简单Java缓存类的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JavaScript 所有数据类型
- 下一篇: 大厂Java岗面试心得记录