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