自己实现简单Java缓存类
需求分析
項目中經(jīng)常會遇到這種場景:一份數(shù)據(jù)需要在多處共享,有些數(shù)據(jù)還有時效性,過期自動失效。比如手機驗證碼,發(fā)送之后需要緩存起來,然后處于安全性考慮,一般還要設(shè)置有效期,到期自動失效。我們怎么實現(xiàn)這樣的功能呢?
解決方案
代碼
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: 簡單的內(nèi)存緩存工具類*/ 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);//設(shè)置過期時間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 {//不設(shè)置過期時間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) {//清除原緩存數(shù)據(jù)Entity entity = map.remove(key);if (entity == null) {return null;}//清除原鍵值對定時器if (entity.future != null) {entity.future.cancel(true);}return (T) entity.value;}/*** 查詢當(dāng)前緩存的鍵值對數(shù)量** @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+定時器線程池實現(xiàn),map用于存儲鍵值對數(shù)據(jù),map的value是Cache的內(nèi)部類對象Entity,Entity包含value和該鍵值對的生命周期定時器Future。Cache類對外只提供了幾個同步方法:
| put(key, value) | 插入緩存數(shù)據(jù) |
| put(key, value, expire) | 插入帶過期時間的緩存數(shù)據(jù), expire: 過期時間,單位:毫秒 |
| get(key) | 獲取緩存數(shù)據(jù) |
| remove(key) | 刪除緩存數(shù)據(jù) |
| size() | 查詢當(dāng)前緩存記錄數(shù) |
當(dāng)添加鍵值對數(shù)據(jù)的時候,首先會調(diào)用remove()方法,清除掉原來相同key的數(shù)據(jù),并取消對應(yīng)的定時清除任務(wù),然后添加新數(shù)據(jù)到map中,并且,如果設(shè)置了有效時間,則添加對應(yīng)的定時清除任務(wù)到定時器線程池。
測試
測試代碼如下:
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";//不設(shè)置過期時間System.out.println("***********不設(shè)置過期時間**********");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));//設(shè)置過期時間System.out.println("\n***********設(shè)置過期時間**********");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讀寫性能測試************");//創(chuàng)建有10個線程的線程池,將1000000次操作分10次添加到線程池int threads = 10;ExecutorService pool = Executors.newFixedThreadPool(threads);//每批操作數(shù)量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();});}//等待全部線程執(zhí)行完成,打印執(zhí)行時間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();});}//等待全部線程執(zhí)行完成,打印執(zhí)行時間latch.await();System.out.printf("查詢耗時:%dms\n", System.currentTimeMillis() - start);}System.out.println("當(dāng)前緩存容量:" + Cache.size());} }測試結(jié)果
***********不設(shè)置過期時間********** key:id, value:123 key:id, value:123 key:id, value:null***********設(shè)置過期時間********** key:id, value:123456 key:id, value:null***********100w讀寫性能測試************ 添加耗時:1729ms 查詢耗時:283ms 當(dāng)前緩存容量:1000000測試程序使用有10個線程的線程池來模擬并發(fā),分別執(zhí)行一百萬次添加和查詢操作,時間大約在兩秒左右,表現(xiàn)還不錯,每秒近百萬讀寫應(yīng)該還是可以滿足大多數(shù)高并發(fā)場景的 ^^
備注:如果對緩存失效延遲非常敏感,不能容忍百萬數(shù)據(jù)亞秒級失效延遲,必須保證嚴(yán)格失效時間的話,可以參考另一版實現(xiàn)(數(shù)據(jù)實體加入了過期時間,每次取出數(shù)據(jù)時會先做判斷)。地址:https://github.com/lixk/ECache
總結(jié)
以上是生活随笔為你收集整理的自己实现简单Java缓存类的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JavaScript 所有数据类型
- 下一篇: 大厂Java岗面试心得记录