AVt天堂网 手机版,亚洲va久久久噜噜噜久久4399,天天综合亚洲色在线精品,亚洲一级Av无码毛片久久精品

當(dāng)前位置:首頁 > 科技  > 軟件

十分鐘手?jǐn)]一款線程安全的高性能通用緩存組件!

來源: 責(zé)編: 時(shí)間:2024-06-11 08:43:40 182觀看
導(dǎo)讀在實(shí)際工作中,有一種非常普遍的并發(fā)場景:那就是讀多寫少的場景。在這種場景下,為了優(yōu)化程序的性能,我們經(jīng)常使用緩存來提高應(yīng)用的訪問性能。因?yàn)榫彺娣浅_m合使用在讀多寫少的場景中。在并發(fā)場景中,Java SDK中提供了ReadWr

TYB28資訊網(wǎng)——每日最新資訊28at.com

在實(shí)際工作中,有一種非常普遍的并發(fā)場景:那就是讀多寫少的場景。在這種場景下,為了優(yōu)化程序的性能,我們經(jīng)常使用緩存來提高應(yīng)用的訪問性能。因?yàn)榫彺娣浅_m合使用在讀多寫少的場景中。TYB28資訊網(wǎng)——每日最新資訊28at.com

在并發(fā)場景中,Java SDK中提供了ReadWriteLock來滿足讀多寫少的場景。本文我們就來說說使用ReadWriteLock如何實(shí)現(xiàn)一個(gè)通用的緩存中心。TYB28資訊網(wǎng)——每日最新資訊28at.com

本文涉及的知識點(diǎn)有:TYB28資訊網(wǎng)——每日最新資訊28at.com

TYB28資訊網(wǎng)——每日最新資訊28at.com

讀寫鎖

說起讀寫鎖,相信小伙伴們并不陌生。總體來說,讀寫鎖需要遵循以下原則:TYB28資訊網(wǎng)——每日最新資訊28at.com

  • 一個(gè)共享變量允許同時(shí)被多個(gè)讀線程讀取到。
  • 一個(gè)共享變量在同一時(shí)刻只能被一個(gè)寫線程進(jìn)行寫操作。
  • 一個(gè)共享變量在被寫線程執(zhí)行寫操作時(shí),此時(shí)這個(gè)共享變量不能被讀線程執(zhí)行讀操作。

這里,需要小伙伴們注意的是:讀寫鎖和互斥鎖的一個(gè)重要的區(qū)別就是:讀寫鎖允許多個(gè)線程同時(shí)讀共享變量,而互斥鎖不允許。所以,在高并發(fā)場景下,讀寫鎖的性能要高于互斥鎖。但是,讀寫鎖的寫操作是互斥的,也就是說,使用讀寫鎖時(shí),一個(gè)共享變量在被寫線程執(zhí)行寫操作時(shí),此時(shí)這個(gè)共享變量不能被讀線程執(zhí)行讀操作。TYB28資訊網(wǎng)——每日最新資訊28at.com

讀寫鎖支持公平模式和非公平模式,具體是在ReentrantReadWriteLock的構(gòu)造方法中傳遞一個(gè)boolean類型的變量來控制。TYB28資訊網(wǎng)——每日最新資訊28at.com

public ReentrantReadWriteLock(boolean fair) {    sync = fair ? new FairSync() : new NonfairSync();    readerLock = new ReadLock(this);    writerLock = new WriteLock(this);}

另外,需要注意的一點(diǎn)是:在讀寫鎖中,讀鎖調(diào)用newCondition()會拋出UnsupportedOperationException異常,也就是說:讀鎖不支持條件變量。TYB28資訊網(wǎng)——每日最新資訊28at.com

緩存實(shí)現(xiàn)

這里,我們使用ReadWriteLock快速實(shí)現(xiàn)一個(gè)緩存的通用工具類,總體代碼如下所示。TYB28資訊網(wǎng)——每日最新資訊28at.com

public class ReadWriteLockCache<K,V> {    private final Map<K, V> m = new HashMap<>();    private final ReadWriteLock rwl = new ReentrantReadWriteLock();    // 讀鎖    private final Lock r = rwl.readLock();    // 寫鎖    private final Lock w = rwl.writeLock();    // 讀緩存    public V get(K key) {        r.lock();        try { return m.get(key); }        finally { r.unlock(); }    }    // 寫緩存    public V put(K key, V value) {        w.lock();        try { return m.put(key, value); }        finally { w.unlock(); }    }}

可以看到,在ReadWriteLockCache中,我們定義了兩個(gè)泛型類型,K代表緩存的Key,V代表緩存的value。在ReadWriteLockCache類的內(nèi)部,我們使用Map來緩存相應(yīng)的數(shù)據(jù),小伙伴都都知道HashMap并不是線程安全的類。TYB28資訊網(wǎng)——每日最新資訊28at.com

所以,這里使用了讀寫鎖來保證線程的安全性,例如,我們在get()方法中使用了讀鎖,get()方法可以被多個(gè)線程同時(shí)執(zhí)行讀操作;put()方法內(nèi)部使用寫鎖,也就是說,put()方法在同一時(shí)刻只能有一個(gè)線程對緩存進(jìn)行寫操作。TYB28資訊網(wǎng)——每日最新資訊28at.com

這里需要注意的是:無論是讀鎖還是寫鎖,鎖的釋放操作都需要放到finally{}代碼塊中。TYB28資訊網(wǎng)——每日最新資訊28at.com

在以往的經(jīng)驗(yàn)中,有兩種向緩存中加載數(shù)據(jù)的方式,一種是:項(xiàng)目啟動時(shí),將數(shù)據(jù)全量加載到緩存中,一種是在項(xiàng)目運(yùn)行期間,按需加載所需要的緩存數(shù)據(jù)。TYB28資訊網(wǎng)——每日最新資訊28at.com

TYB28資訊網(wǎng)——每日最新資訊28at.com

接下來,我們就分別來看看全量加載緩存和按需加載緩存的方式。TYB28資訊網(wǎng)——每日最新資訊28at.com

全量加載緩存

全量加載緩存相對來說比較簡單,就是在項(xiàng)目啟動的時(shí)候,將數(shù)據(jù)一次性加載到緩存中,這種情況適用于緩存數(shù)據(jù)量不大,數(shù)據(jù)變動不頻繁的場景,例如:可以緩存一些系統(tǒng)中的數(shù)據(jù)字典等信息。整個(gè)緩存加載的大體流程如下所示。TYB28資訊網(wǎng)——每日最新資訊28at.com

TYB28資訊網(wǎng)——每日最新資訊28at.com

將數(shù)據(jù)全量加載到緩存后,后續(xù)就可以直接從緩存中讀取相應(yīng)的數(shù)據(jù)了。TYB28資訊網(wǎng)——每日最新資訊28at.com

全量加載緩存的代碼實(shí)現(xiàn)比較簡單,這里,我就直接使用如下代碼進(jìn)行演示。TYB28資訊網(wǎng)——每日最新資訊28at.com

public class ReadWriteLockCache<K,V> {    private final Map<K, V> m = new HashMap<>();    private final ReadWriteLock rwl = new ReentrantReadWriteLock();    // 讀鎖    private final Lock r = rwl.readLock();    // 寫鎖    private final Lock w = rwl.writeLock();        public ReadWriteLockCache(){        //查詢數(shù)據(jù)庫        List<Field<K, V>> list = .....;        if(!CollectionUtils.isEmpty(list)){            list.parallelStream().forEach((f) ->{    m.put(f.getK(), f.getV);   });        }    }    // 讀緩存    public V get(K key) {        r.lock();        try { return m.get(key); }        finally { r.unlock(); }    }    // 寫緩存    public V put(K key, V value) {        w.lock();        try { return m.put(key, value); }        finally { w.unlock(); }    }}

按需加載緩存

按需加載緩存也可以叫作懶加載,就是說:需要加載的時(shí)候才會將數(shù)據(jù)加載到緩存。具體來說:就是程序啟動的時(shí)候,不會將數(shù)據(jù)加載到緩存,當(dāng)運(yùn)行時(shí),需要查詢某些數(shù)據(jù),首先檢測緩存中是否存在需要的數(shù)據(jù),如果存在,則直接讀取緩存中的數(shù)據(jù),如果不存在,則到數(shù)據(jù)庫中查詢數(shù)據(jù),并將數(shù)據(jù)寫入緩存。后續(xù)的讀取操作,因?yàn)榫彺嬷幸呀?jīng)存在了相應(yīng)的數(shù)據(jù),直接返回緩存的數(shù)據(jù)即可。TYB28資訊網(wǎng)——每日最新資訊28at.com

TYB28資訊網(wǎng)——每日最新資訊28at.com

這種查詢緩存的方式適用于大多數(shù)緩存數(shù)據(jù)的場景。TYB28資訊網(wǎng)——每日最新資訊28at.com

我們可以使用如下代碼來表示按需查詢緩存的業(yè)務(wù)。TYB28資訊網(wǎng)——每日最新資訊28at.com

class ReadWriteLockCache<K,V> {    private final Map<K, V> m = new HashMap<>();    private final ReadWriteLock rwl =  new ReentrantReadWriteLock();    private final Lock r = rwl.readLock();    private final Lock w = rwl.writeLock();    V get(K key) {        V v = null;        //讀緩存        r.lock();                try {            v = m.get(key);        } finally{            r.unlock();            }        //緩存中存在,返回        if(v != null) {              return v;        }          //緩存中不存在,查詢數(shù)據(jù)庫        w.lock();             try {     //再次驗(yàn)證緩存中是否存在數(shù)據(jù)            v = m.get(key);            if(v == null){                 //查詢數(shù)據(jù)庫                v=從數(shù)據(jù)庫中查詢出來的數(shù)據(jù)                m.put(key, v);            }        } finally{            w.unlock();        }        return v;     }}

這里,在get()方法中,首先從緩存中讀取數(shù)據(jù),此時(shí),我們對查詢緩存的操作添加了讀鎖,查詢返回后,進(jìn)行解鎖操作。判斷緩存中返回的數(shù)據(jù)是否為空,不為空,則直接返回?cái)?shù)據(jù);如果為空,則獲取寫鎖,之后再次從緩存中讀取數(shù)據(jù),如果緩存中不存在數(shù)據(jù),則查詢數(shù)據(jù)庫,將結(jié)果數(shù)據(jù)寫入緩存,釋放寫鎖。最終返回結(jié)果數(shù)據(jù)。TYB28資訊網(wǎng)——每日最新資訊28at.com

這里,有小伙伴可能會問:為啥程序都已經(jīng)添加寫鎖了,在寫鎖內(nèi)部為啥還要查詢一次緩存呢?TYB28資訊網(wǎng)——每日最新資訊28at.com

這是因?yàn)樵诟卟l(fā)的場景下,可能會存在多個(gè)線程來競爭寫鎖的現(xiàn)象。例如:第一次執(zhí)行g(shù)et()方法時(shí),緩存中的數(shù)據(jù)為空。如果此時(shí)有三個(gè)線程同時(shí)調(diào)用get()方法,同時(shí)運(yùn)行到 w.lock()代碼處,由于寫鎖的排他性。此時(shí)只有一個(gè)線程會獲取到寫鎖,其他兩個(gè)線程則阻塞在w.lock()處。獲取到寫鎖的線程繼續(xù)往下執(zhí)行查詢數(shù)據(jù)庫,將數(shù)據(jù)寫入緩存,之后釋放寫鎖。TYB28資訊網(wǎng)——每日最新資訊28at.com

此時(shí),另外兩個(gè)線程競爭寫鎖,某個(gè)線程會獲取到鎖,繼續(xù)往下執(zhí)行,如果在w.lock()后沒有 v = m.get(key); 再次查詢緩存的數(shù)據(jù),則這個(gè)線程會直接查詢數(shù)據(jù)庫,將數(shù)據(jù)寫入緩存后釋放寫鎖。最后一個(gè)線程同樣會按照這個(gè)流程執(zhí)行。TYB28資訊網(wǎng)——每日最新資訊28at.com

這里,實(shí)際上第一個(gè)線程已經(jīng)查詢過數(shù)據(jù)庫,并且將數(shù)據(jù)寫入緩存了,其他兩個(gè)線程就沒必要再次查詢數(shù)據(jù)庫了,直接從緩存中查詢出相應(yīng)的數(shù)據(jù)即可。TYB28資訊網(wǎng)——每日最新資訊28at.com

所以,在w.lock()后添加 v = m.get(key); 再次查詢緩存的數(shù)據(jù),能夠有效的減少高并發(fā)場景下重復(fù)查詢數(shù)據(jù)庫的問題,提升系統(tǒng)的性能。TYB28資訊網(wǎng)——每日最新資訊28at.com

讀寫鎖的升降級

關(guān)于鎖的升降級,小伙伴們需要注意的是:在ReadWriteLock中,鎖是不支持升級的,因?yàn)樽x鎖還未釋放時(shí),此時(shí)獲取寫鎖,就會導(dǎo)致寫鎖永久等待,相應(yīng)的線程也會被阻塞而無法喚醒。TYB28資訊網(wǎng)——每日最新資訊28at.com

雖然不支持鎖升級,但是ReadWriteLock支持鎖降級,例如,我們來看看官方的ReentrantReadWriteLock示例,如下所示。TYB28資訊網(wǎng)——每日最新資訊28at.com

class CachedData {    Object data;    volatile boolean cacheValid;    final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();    void processCachedData() {        rwl.readLock().lock();        if (!cacheValid) {            // Must release read lock before acquiring write lock            rwl.readLock().unlock();            rwl.writeLock().lock();            try {                // Recheck state because another thread might have                // acquired write lock and changed state before we did.                if (!cacheValid) {                    data = ...                    cacheValid = true;                }                // Downgrade by acquiring read lock before releasing write lock                rwl.readLock().lock();            } finally {                rwl.writeLock().unlock(); // Unlock write, still hold read            }        }        try {            use(data);        } finally {            rwl.readLock().unlock();        }    }}}

數(shù)據(jù)同步問題

首先,這里說的數(shù)據(jù)同步指的是數(shù)據(jù)源和數(shù)據(jù)緩存之間的數(shù)據(jù)同步,說的再直接一點(diǎn),就是數(shù)據(jù)庫和緩存之間的數(shù)據(jù)同步。TYB28資訊網(wǎng)——每日最新資訊28at.com

這里,我們可以采取三種方案來解決數(shù)據(jù)同步的問題,如下圖所示:TYB28資訊網(wǎng)——每日最新資訊28at.com

TYB28資訊網(wǎng)——每日最新資訊28at.com

超時(shí)機(jī)制

這個(gè)比較好理解,就是在向緩存寫入數(shù)據(jù)的時(shí)候,給一個(gè)超時(shí)時(shí)間,當(dāng)緩存超時(shí)后,緩存的數(shù)據(jù)會自動從緩存中移除,此時(shí)程序再次訪問緩存時(shí),由于緩存中不存在相應(yīng)的數(shù)據(jù),查詢數(shù)據(jù)庫得到數(shù)據(jù)后,再將數(shù)據(jù)寫入緩存。TYB28資訊網(wǎng)——每日最新資訊28at.com

定時(shí)更新緩存

這種方案是超時(shí)機(jī)制的增強(qiáng)版,在向緩存中寫入數(shù)據(jù)的時(shí)候,同樣給一個(gè)超時(shí)時(shí)間。與超時(shí)機(jī)制不同的是,在程序后臺單獨(dú)啟動一個(gè)線程,定時(shí)查詢數(shù)據(jù)庫中的數(shù)據(jù),然后將數(shù)據(jù)寫入緩存中,這樣能夠在一定程度上避免緩存的穿透問題。TYB28資訊網(wǎng)——每日最新資訊28at.com

實(shí)時(shí)更新緩存

這種方案能夠做到數(shù)據(jù)庫中的數(shù)據(jù)與緩存的數(shù)據(jù)是實(shí)時(shí)同步的,可以使用阿里開源的Canal框架實(shí)現(xiàn)MySQL數(shù)據(jù)庫與緩存數(shù)據(jù)的實(shí)時(shí)同步。也可以使用我個(gè)人開源的mykit-data框架哦(推薦使用)~~TYB28資訊網(wǎng)——每日最新資訊28at.com

mykit-data開源地址:TYB28資訊網(wǎng)——每日最新資訊28at.com

  • https://github.com/sunshinelyz/mykit-data。
  • https://gitee.com/binghe001/mykit-data。

本文鏈接:http://www.tebozhan.com/showinfo-26-92928-0.html十分鐘手?jǐn)]一款線程安全的高性能通用緩存組件!

聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。郵件:2376512515@qq.com

上一篇: Oh-My-Posh: 可定制且低延遲的跨平臺/跨Shell提示符渲染器

下一篇: 前端輪播圖,搞定!

標(biāo)簽:
  • 熱門焦點(diǎn)
Top