String.intern()來優化使用Redis

本文記錄使用String.intern()來優化使用Redis作爲查詢緩存的場景.


使用場景

在一個接口中,該接口被多個線程併發訪問,該接口主要做了以下工作:查詢的時候是根據廣告的類型查詢符合該類型的廣告,如果查詢到廣告,那麼就返回該類型廣告列表。同時,由於請求量比較的大,爲了增加查詢速度,減輕數據庫的負擔,我們在該層加入Redis。

我們會這樣做,首先,我們去Redis查詢該類型的廣告,如果存在那麼就直接返回,如果不存在,那麼我們需要爲該廣告類型加鎖,進行鎖定,然後進行二次查詢Redis(二次判空,保證查詢排隊的線程,只要有一個查詢到了,那麼其他的就不用查庫了),然後查詢數據庫,再存入到Redis 中,返回即可。其他的線程,如果當時在排隊,那麼在二次判空的時候就可以拿到值,其他的則在第一次查詢Redis中直接拿到了值。

案例分析

但是,要注意,在接口中我們是在線程內部的,我們鎖定的只是一個字符串對象。首先,相同值的字符串,也可能是不同的對象;其次,該場景下字符串爲局部變量(需注意是字面量賦值還是new對象),在線程內部,由於線程的棧封閉性,我們鎖定的該字符串值,其他線程並不知道。

所以,我們需要一種策略,值相同的字符串就是一個對象,同時又是線程可見性的,那麼字符串常量池就是一個很好的媒介,我們可以使用intern方法得到字符串常量池的引用,這樣就保證了字符串值相同,那麼就是一個對象,同時又是線程可見的。

但是,我們最好還是不要直接用字符串的intern方法,首先在Jdk1.6以及之前,字符串常量池是存儲在永久代中的,也就是方法區中的,如果頻繁使用該方法,那麼就會造成該區域內存佔有過大,造成垃圾收集器的GC,從而影響程序的運行。Java有一個很好的工具庫, Guava ,其中封裝了很多的工具類,其中很多平時都很常用,其中就有一個類,

Interner<String> pool = Interners.newWeakInterner();

該類對 intern 做了很多的優化,使用弱引用包裝了你傳入的字符串類型,所以,這樣就不會對內存造成較大的影響,可以使用該類的 pool.intern(str)來進行對字符串intern。 這樣就解決了內存的問題。

解決方案

僞碼:

if (redis 存在) {
    // 直接return;
} else { 
    // 不存在
    Interner<String> pool = Interners.newWeakInterner();
    synchronized (pool.intern(str)) {
        if(redis 存在){
            return;
        }else{
            // 查庫,入redis,返回
        }
    }
}

問題擴展

synchronized關鍵字針對共享變量,方法,類加鎖;針對局部變量或者對象,無法起到鎖的效果

深入理解String#intern

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章