Java實現(xiàn)一個簡單的緩存

cache

閱讀原文請訪問我的博客BrightLoong's Blog
??緩存是在web開發(fā)中經(jīng)常用到的,將程序經(jīng)常使用到或調(diào)用到的對象存在內(nèi)存中,或者是耗時較長但又不具有實時性的查詢數(shù)據(jù)放入內(nèi)存中,在一定程度上可以提高性能和效率。下面我實現(xiàn)了一個簡單的緩存,步驟如下。

創(chuàng)建緩存對象EntityCache.java

public class EntityCache {
    /**
     * 保存的數(shù)據(jù)
     */
    private  Object datas;
    
    /**
     * 設置數(shù)據(jù)失效時間,為0表示永不失效
     */
    private  long timeOut;
    
    /**
     * 最后刷新時間
     */
    private  long lastRefeshTime;
    
    public EntityCache(Object datas, long timeOut, long lastRefeshTime) {
        this.datas = datas;
        this.timeOut = timeOut;
        this.lastRefeshTime = lastRefeshTime;
    }
    public Object getDatas() {
        return datas;
    }
    public void setDatas(Object datas) {
        this.datas = datas;
    }
    public long getTimeOut() {
        return timeOut;
    }
    public void setTimeOut(long timeOut) {
        this.timeOut = timeOut;
    }
    public long getLastRefeshTime() {
        return lastRefeshTime;
    }
    public void setLastRefeshTime(long lastRefeshTime) {
        this.lastRefeshTime = lastRefeshTime;
    }
    
    
}

定義緩存操作接口,ICacheManager.java

public interface ICacheManager {
    /**
     * 存入緩存
     * @param key
     * @param cache
     */
    void putCache(String key, EntityCache cache);
    
    /**
     * 存入緩存
     * @param key
     * @param cache
     */
    void putCache(String key, Object datas, long timeOut);
    
    /**
     * 獲取對應緩存
     * @param key
     * @return
     */
    EntityCache getCacheByKey(String key);
    
    /**
     * 獲取對應緩存
     * @param key
     * @return
     */
    Object getCacheDataByKey(String key);
    
    /**
     * 獲取所有緩存
     * @param key
     * @return
     */
    Map<String, EntityCache> getCacheAll();
    
    /**
     * 判斷是否在緩存中
     * @param key
     * @return
     */
    boolean isContains(String key);
    
    /**
     * 清除所有緩存
     */
    void clearAll();
    
    /**
     * 清除對應緩存
     * @param key
     */
    void clearByKey(String key);
    
    /**
     * 緩存是否超時失效
     * @param key
     * @return
     */
    boolean isTimeOut(String key);
    
    /**
     * 獲取所有key
     * @return
     */
    Set<String> getAllKeys();
}

實現(xiàn)接口ICacheManager,CacheManagerImpl.java

??這里我使用了ConcurrentHashMap來保存緩存,本來以為這樣就是線程安全的,其實不然,在后面的測試中會發(fā)現(xiàn)它并不是線程安全的。

public class CacheManagerImpl implements ICacheManager {
    private static Map<String, EntityCache> caches = new ConcurrentHashMap<String, EntityCache>();

    /**
     * 存入緩存
     * @param key
     * @param cache
     */
    public void putCache(String key, EntityCache cache) {
        caches.put(key, cache);
    }
    
    /**
     * 存入緩存
     * @param key
     * @param cache
     */
    public void putCache(String key, Object datas, long timeOut) {
        timeOut = timeOut > 0 ? timeOut : 0L;
        putCache(key, new EntityCache(datas, timeOut, System.currentTimeMillis()));
    }
    
    /**
     * 獲取對應緩存
     * @param key
     * @return
     */
    public EntityCache getCacheByKey(String key) {
        if (this.isContains(key)) {
            return caches.get(key);
        }
        return null;
    }
    
    /**
     * 獲取對應緩存
     * @param key
     * @return
     */
    public Object getCacheDataByKey(String key) {
        if (this.isContains(key)) {
            return caches.get(key).getDatas();
        }
        return null;
    }

    /**
     * 獲取所有緩存
     * @param key
     * @return
     */
    public Map<String, EntityCache> getCacheAll() {
        return caches;
    }
    
    /**
     * 判斷是否在緩存中
     * @param key
     * @return
     */
    public boolean isContains(String key) {
        return caches.containsKey(key);
    }

    /**
     * 清除所有緩存
     */
    public void clearAll() {
        caches.clear();
    }
    
    /**
     * 清除對應緩存
     * @param key
     */
    public void clearByKey(String key) {
        if (this.isContains(key)) {
            caches.remove(key);
        }
    }
    
    /**
     * 緩存是否超時失效
     * @param key
     * @return
     */
    public boolean isTimeOut(String key) {
        if (!caches.containsKey(key)) {
            return true;
        }
        EntityCache cache = caches.get(key);
        long timeOut = cache.getTimeOut();
        long lastRefreshTime = cache.getLastRefeshTime();
        if (timeOut == 0 || System.currentTimeMillis() - lastRefreshTime >= timeOut) {
            return true;
        }
        return false;
    }

    /**
     * 獲取所有key
     * @return
     */
    public Set<String>  getAllKeys() {
        return caches.keySet();
    }
}

CacheListener.java,監(jiān)聽失效數(shù)據(jù)并移除。

public class CacheListener{
    Logger logger = Logger.getLogger("cacheLog");
    private CacheManagerImpl cacheManagerImpl;
    public CacheListener(CacheManagerImpl cacheManagerImpl) {
        this.cacheManagerImpl = cacheManagerImpl;
    }
    
    public void startListen() {
        new Thread(){
            public void run() {
                while (true) {
                    for(String key : cacheManagerImpl.getAllKeys()) {
                        if (cacheManagerImpl.isTimeOut(key)) {
                         cacheManagerImpl.clearByKey(key);
                         logger.info(key + "緩存被清除");
                     }
                    } 
                }
            }  
        }.start();
    
    }
}

測試類TestCache.java

public class TestCache {
    Logger logger = Logger.getLogger("cacheLog");
    /**
     * 測試緩存和緩存失效
     */
    @Test
    public void testCacheManager() {
        CacheManagerImpl cacheManagerImpl = new CacheManagerImpl();
        cacheManagerImpl.putCache("test", "test", 10 * 1000L);
        cacheManagerImpl.putCache("myTest", "myTest", 15 * 1000L);
        CacheListener cacheListener = new CacheListener(cacheManagerImpl);
        cacheListener.startListen();
        logger.info("test:" + cacheManagerImpl.getCacheByKey("test").getDatas());
        logger.info("myTest:" + cacheManagerImpl.getCacheByKey("myTest").getDatas());
        try {
            TimeUnit.SECONDS.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        logger.info("test:" + cacheManagerImpl.getCacheByKey("test"));
        logger.info("myTest:" + cacheManagerImpl.getCacheByKey("myTest"));
    }
    
    /**
     * 測試線程安全
     */
    @Test
    public void testThredSafe() {
        final String key = "thread";
        final CacheManagerImpl cacheManagerImpl = new CacheManagerImpl();
        ExecutorService exec = Executors.newCachedThreadPool();
        for (int i = 0; i < 100; i++) {
            exec.execute(new Runnable() {
                public void run() {
                        if (!cacheManagerImpl.isContains(key)) {
                            cacheManagerImpl.putCache(key, 1, 0);
                        } else {
                            //因為+1和賦值操作不是原子性的,所以把它用synchronize塊包起來
                            synchronized (cacheManagerImpl) {
                               int value = (Integer) cacheManagerImpl.getCacheDataByKey(key) + 1; 
                               cacheManagerImpl.putCache(key,value , 0);
                            }
                        }
                }
            });
        }
        exec.shutdown();  
        try {
            exec.awaitTermination(1, TimeUnit.DAYS);
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }  
        
        logger.info(cacheManagerImpl.getCacheDataByKey(key).toString());
    }
}

testCacheManager()輸出結果如下:

2017-4-17 10:33:51 io.github.brightloong.cache.TestCache testCacheManager
信息: test:test
2017-4-17 10:33:51 io.github.brightloong.cache.TestCache testCacheManager
信息: myTest:myTest
2017-4-17 10:34:01 io.github.brightloong.cache.CacheListener$1 run
信息: test緩存被清除
2017-4-17 10:34:06 io.github.brightloong.cache.CacheListener$1 run
信息: myTest緩存被清除
2017-4-17 10:34:11 io.github.brightloong.cache.TestCache testCacheManager
信息: test:null
2017-4-17 10:34:11 io.github.brightloong.cache.TestCache testCacheManager
信息: myTest:null

testThredSafe()輸出結果如下(選出了各種結果中的一個舉例):

2017-4-17 10:35:36 io.github.brightloong.cache.TestCache testThredSafe
信息: 96

可以看到并不是預期的結果100,為什么呢?ConcurrentHashMap只能保證單次操作的原子性,但是當復合使用的時候,沒辦法保證復合操作的原子性,以下代碼:

if (!cacheManagerImpl.isContains(key)) {
                            cacheManagerImpl.putCache(key, 1, 0);
                        }

多線程的時候回重復更新value,設置為1,所以出現(xiàn)結果不是預期的100。所以辦法就是在CacheManagerImpl.java中都加上synchronized,但是這樣一來相當于操作都是串行,使用ConcurrentHashMap也沒有什么意義,不過只是簡單的緩存還是可以的。或者對測試方法中的run里面加上synchronized塊也行,都是大同小異。更高效的方法我暫時也想不出來,希望大家能多多指教。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,362評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,577評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,486評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,852評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,600評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,944評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,944評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,108評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,652評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,385評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,616評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,111評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,798評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,205評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,537評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,334評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,570評論 2 379

推薦閱讀更多精彩內(nèi)容

  • 一個簡單的單例示例 單例模式可能是大家經(jīng)常接觸和使用的一個設計模式,你可能會這么寫 publicclassUnsa...
    Martin說閱讀 2,257評論 0 6
  • 從三月份找實習到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發(fā)崗...
    時芥藍閱讀 42,339評論 11 349
  • Java8張圖 11、字符串不變性 12、equals()方法、hashCode()方法的區(qū)別 13、...
    Miley_MOJIE閱讀 3,725評論 0 11
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內(nèi)部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,737評論 18 399
  • 獨立思考 在生活中,想必你或多或少也有過這樣的經(jīng)歷:跟同學、朋友或者同事外出一起吃飯的時候,當你讓ta們點菜時,t...
    彈指情屋閱讀 324評論 0 1