成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

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

開(kāi)發(fā) 前端
在并發(fā)場(chǎng)景中,Java SDK中提供了ReadWriteLock來(lái)滿(mǎn)足讀多寫(xiě)少的場(chǎng)景。本文我們就來(lái)說(shuō)說(shuō)使用ReadWriteLock如何實(shí)現(xiàn)一個(gè)通用的緩存中心。

在實(shí)際工作中,有一種非常普遍的并發(fā)場(chǎng)景:那就是讀多寫(xiě)少的場(chǎng)景。在這種場(chǎng)景下,為了優(yōu)化程序的性能,我們經(jīng)常使用緩存來(lái)提高應(yīng)用的訪(fǎng)問(wèn)性能。因?yàn)榫彺娣浅_m合使用在讀多寫(xiě)少的場(chǎng)景中。

在并發(fā)場(chǎng)景中,Java SDK中提供了ReadWriteLock來(lái)滿(mǎn)足讀多寫(xiě)少的場(chǎng)景。本文我們就來(lái)說(shuō)說(shuō)使用ReadWriteLock如何實(shí)現(xiàn)一個(gè)通用的緩存中心。

本文涉及的知識(shí)點(diǎn)有:

讀寫(xiě)鎖

說(shuō)起讀寫(xiě)鎖,相信小伙伴們并不陌生。總體來(lái)說(shuō),讀寫(xiě)鎖需要遵循以下原則:

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

這里,需要小伙伴們注意的是:讀寫(xiě)鎖和互斥鎖的一個(gè)重要的區(qū)別就是:讀寫(xiě)鎖允許多個(gè)線(xiàn)程同時(shí)讀共享變量,而互斥鎖不允許。所以,在高并發(fā)場(chǎng)景下,讀寫(xiě)鎖的性能要高于互斥鎖。但是,讀寫(xiě)鎖的寫(xiě)操作是互斥的,也就是說(shuō),使用讀寫(xiě)鎖時(shí),一個(gè)共享變量在被寫(xiě)線(xiàn)程執(zhí)行寫(xiě)操作時(shí),此時(shí)這個(gè)共享變量不能被讀線(xiàn)程執(zhí)行讀操作。

讀寫(xiě)鎖支持公平模式和非公平模式,具體是在ReentrantReadWriteLock的構(gòu)造方法中傳遞一個(gè)boolean類(lèi)型的變量來(lái)控制。

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

另外,需要注意的一點(diǎn)是:在讀寫(xiě)鎖中,讀鎖調(diào)用newCondition()會(huì)拋出UnsupportedOperationException異常,也就是說(shuō):讀鎖不支持條件變量。

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

這里,我們使用ReadWriteLock快速實(shí)現(xiàn)一個(gè)緩存的通用工具類(lèi),總體代碼如下所示。

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();
    // 寫(xiě)鎖
    private final Lock w = rwl.writeLock();
    // 讀緩存
    public V get(K key) {
        r.lock();
        try { return m.get(key); }
        finally { r.unlock(); }
    }
    // 寫(xiě)緩存
    public V put(K key, V value) {
        w.lock();
        try { return m.put(key, value); }
        finally { w.unlock(); }
    }
}

可以看到,在ReadWriteLockCache中,我們定義了兩個(gè)泛型類(lèi)型,K代表緩存的Key,V代表緩存的value。在ReadWriteLockCache類(lèi)的內(nèi)部,我們使用Map來(lái)緩存相應(yīng)的數(shù)據(jù),小伙伴都都知道HashMap并不是線(xiàn)程安全的類(lèi)。

所以,這里使用了讀寫(xiě)鎖來(lái)保證線(xiàn)程的安全性,例如,我們?cè)趃et()方法中使用了讀鎖,get()方法可以被多個(gè)線(xiàn)程同時(shí)執(zhí)行讀操作;put()方法內(nèi)部使用寫(xiě)鎖,也就是說(shuō),put()方法在同一時(shí)刻只能有一個(gè)線(xiàn)程對(duì)緩存進(jìn)行寫(xiě)操作。

這里需要注意的是:無(wú)論是讀鎖還是寫(xiě)鎖,鎖的釋放操作都需要放到finally{}代碼塊中。

在以往的經(jīng)驗(yàn)中,有兩種向緩存中加載數(shù)據(jù)的方式,一種是:項(xiàng)目啟動(dòng)時(shí),將數(shù)據(jù)全量加載到緩存中,一種是在項(xiàng)目運(yùn)行期間,按需加載所需要的緩存數(shù)據(jù)。

接下來(lái),我們就分別來(lái)看看全量加載緩存和按需加載緩存的方式。

全量加載緩存

全量加載緩存相對(duì)來(lái)說(shuō)比較簡(jiǎn)單,就是在項(xiàng)目啟動(dòng)的時(shí)候,將數(shù)據(jù)一次性加載到緩存中,這種情況適用于緩存數(shù)據(jù)量不大,數(shù)據(jù)變動(dòng)不頻繁的場(chǎng)景,例如:可以緩存一些系統(tǒng)中的數(shù)據(jù)字典等信息。整個(gè)緩存加載的大體流程如下所示。

將數(shù)據(jù)全量加載到緩存后,后續(xù)就可以直接從緩存中讀取相應(yīng)的數(shù)據(jù)了。

全量加載緩存的代碼實(shí)現(xiàn)比較簡(jiǎn)單,這里,我就直接使用如下代碼進(jìn)行演示。

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();
    // 寫(xiě)鎖
    private final Lock w = rwl.writeLock();
    
    public ReadWriteLockCache(){
        //查詢(xún)數(shù)據(jù)庫(kù)
        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(); }
    }
    // 寫(xiě)緩存
    public V put(K key, V value) {
        w.lock();
        try { return m.put(key, value); }
        finally { w.unlock(); }
    }
}

按需加載緩存

按需加載緩存也可以叫作懶加載,就是說(shuō):需要加載的時(shí)候才會(huì)將數(shù)據(jù)加載到緩存。具體來(lái)說(shuō):就是程序啟動(dòng)的時(shí)候,不會(huì)將數(shù)據(jù)加載到緩存,當(dāng)運(yùn)行時(shí),需要查詢(xún)某些數(shù)據(jù),首先檢測(cè)緩存中是否存在需要的數(shù)據(jù),如果存在,則直接讀取緩存中的數(shù)據(jù),如果不存在,則到數(shù)據(jù)庫(kù)中查詢(xún)數(shù)據(jù),并將數(shù)據(jù)寫(xiě)入緩存。后續(xù)的讀取操作,因?yàn)榫彺嬷幸呀?jīng)存在了相應(yīng)的數(shù)據(jù),直接返回緩存的數(shù)據(jù)即可。

這種查詢(xún)緩存的方式適用于大多數(shù)緩存數(shù)據(jù)的場(chǎng)景。

我們可以使用如下代碼來(lái)表示按需查詢(xún)緩存的業(yè)務(wù)。

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;
        }  
        //緩存中不存在,查詢(xún)數(shù)據(jù)庫(kù)
        w.lock();     
        try {
     //再次驗(yàn)證緩存中是否存在數(shù)據(jù)
            v = m.get(key);
            if(v == null){ 
                //查詢(xún)數(shù)據(jù)庫(kù)
                v=從數(shù)據(jù)庫(kù)中查詢(xún)出來(lái)的數(shù)據(jù)
                m.put(key, v);
            }
        } finally{
            w.unlock();
        }
        return v; 
    }
}

這里,在get()方法中,首先從緩存中讀取數(shù)據(jù),此時(shí),我們對(duì)查詢(xún)緩存的操作添加了讀鎖,查詢(xún)返回后,進(jìn)行解鎖操作。判斷緩存中返回的數(shù)據(jù)是否為空,不為空,則直接返回?cái)?shù)據(jù);如果為空,則獲取寫(xiě)鎖,之后再次從緩存中讀取數(shù)據(jù),如果緩存中不存在數(shù)據(jù),則查詢(xún)數(shù)據(jù)庫(kù),將結(jié)果數(shù)據(jù)寫(xiě)入緩存,釋放寫(xiě)鎖。最終返回結(jié)果數(shù)據(jù)。

這里,有小伙伴可能會(huì)問(wèn):為啥程序都已經(jīng)添加寫(xiě)鎖了,在寫(xiě)鎖內(nèi)部為啥還要查詢(xún)一次緩存呢?

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

此時(shí),另外兩個(gè)線(xiàn)程競(jìng)爭(zhēng)寫(xiě)鎖,某個(gè)線(xiàn)程會(huì)獲取到鎖,繼續(xù)往下執(zhí)行,如果在w.lock()后沒(méi)有 v = m.get(key); 再次查詢(xún)緩存的數(shù)據(jù),則這個(gè)線(xiàn)程會(huì)直接查詢(xún)數(shù)據(jù)庫(kù),將數(shù)據(jù)寫(xiě)入緩存后釋放寫(xiě)鎖。最后一個(gè)線(xiàn)程同樣會(huì)按照這個(gè)流程執(zhí)行。

這里,實(shí)際上第一個(gè)線(xiàn)程已經(jīng)查詢(xún)過(guò)數(shù)據(jù)庫(kù),并且將數(shù)據(jù)寫(xiě)入緩存了,其他兩個(gè)線(xiàn)程就沒(méi)必要再次查詢(xún)數(shù)據(jù)庫(kù)了,直接從緩存中查詢(xún)出相應(yīng)的數(shù)據(jù)即可。

所以,在w.lock()后添加 v = m.get(key); 再次查詢(xún)緩存的數(shù)據(jù),能夠有效的減少高并發(fā)場(chǎng)景下重復(fù)查詢(xún)數(shù)據(jù)庫(kù)的問(wèn)題,提升系統(tǒng)的性能。

讀寫(xiě)鎖的升降級(jí)

關(guān)于鎖的升降級(jí),小伙伴們需要注意的是:在ReadWriteLock中,鎖是不支持升級(jí)的,因?yàn)樽x鎖還未釋放時(shí),此時(shí)獲取寫(xiě)鎖,就會(huì)導(dǎo)致寫(xiě)鎖永久等待,相應(yīng)的線(xiàn)程也會(huì)被阻塞而無(wú)法喚醒。

雖然不支持鎖升級(jí),但是ReadWriteLock支持鎖降級(jí),例如,我們來(lái)看看官方的ReentrantReadWriteLock示例,如下所示。

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ù)同步問(wèn)題

首先,這里說(shuō)的數(shù)據(jù)同步指的是數(shù)據(jù)源和數(shù)據(jù)緩存之間的數(shù)據(jù)同步,說(shuō)的再直接一點(diǎn),就是數(shù)據(jù)庫(kù)和緩存之間的數(shù)據(jù)同步。

這里,我們可以采取三種方案來(lái)解決數(shù)據(jù)同步的問(wèn)題,如下圖所示:

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

這個(gè)比較好理解,就是在向緩存寫(xiě)入數(shù)據(jù)的時(shí)候,給一個(gè)超時(shí)時(shí)間,當(dāng)緩存超時(shí)后,緩存的數(shù)據(jù)會(huì)自動(dòng)從緩存中移除,此時(shí)程序再次訪(fǎng)問(wèn)緩存時(shí),由于緩存中不存在相應(yīng)的數(shù)據(jù),查詢(xún)數(shù)據(jù)庫(kù)得到數(shù)據(jù)后,再將數(shù)據(jù)寫(xiě)入緩存。

定時(shí)更新緩存

這種方案是超時(shí)機(jī)制的增強(qiáng)版,在向緩存中寫(xiě)入數(shù)據(jù)的時(shí)候,同樣給一個(gè)超時(shí)時(shí)間。與超時(shí)機(jī)制不同的是,在程序后臺(tái)單獨(dú)啟動(dòng)一個(gè)線(xiàn)程,定時(shí)查詢(xún)數(shù)據(jù)庫(kù)中的數(shù)據(jù),然后將數(shù)據(jù)寫(xiě)入緩存中,這樣能夠在一定程度上避免緩存的穿透問(wèn)題。

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

這種方案能夠做到數(shù)據(jù)庫(kù)中的數(shù)據(jù)與緩存的數(shù)據(jù)是實(shí)時(shí)同步的,可以使用阿里開(kāi)源的Canal框架實(shí)現(xiàn)MySQL數(shù)據(jù)庫(kù)與緩存數(shù)據(jù)的實(shí)時(shí)同步。也可以使用我個(gè)人開(kāi)源的mykit-data框架哦(推薦使用)~~

mykit-data開(kāi)源地址:

  • https://github.com/sunshinelyz/mykit-data。
  • https://gitee.com/binghe001/mykit-data。
責(zé)任編輯:姜華 來(lái)源: 冰河技術(shù)
相關(guān)推薦

2020-12-17 06:48:21

SQLkafkaMySQL

2019-04-01 14:59:56

負(fù)載均衡服務(wù)器網(wǎng)絡(luò)

2024-10-25 15:56:20

2024-06-19 09:58:29

2021-09-07 09:40:20

Spark大數(shù)據(jù)引擎

2022-06-16 07:31:41

Web組件封裝HTML 標(biāo)簽

2023-04-12 11:18:51

甘特圖前端

2015-09-06 09:22:24

框架搭建快速高效app

2012-07-10 01:22:32

PythonPython教程

2024-05-13 09:28:43

Flink SQL大數(shù)據(jù)

2023-11-30 10:21:48

虛擬列表虛擬列表工具庫(kù)

2023-11-09 14:44:27

Docker鏡像容器

2019-09-16 09:14:51

2009-10-09 14:45:29

VB程序

2023-07-15 18:26:51

LinuxABI

2022-08-26 09:01:07

CSSFlex 布局

2024-11-07 16:09:53

2020-12-11 09:40:10

DevOpsCICD

2015-11-06 11:03:36

2022-04-13 22:01:44

錯(cuò)誤監(jiān)控系統(tǒng)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 天啪| 精品欧美黑人一区二区三区 | 欧美三级电影在线播放 | 操久久| www国产成人 | 国产亚洲一区二区三区在线观看 | 亚洲人人| 亚洲网址在线观看 | 国产a一区二区 | 亚州成人| 人人色视频 | 日本亚洲欧美 | 精品在线一区二区三区 | 99视频在线播放 | 一道本在线 | 日韩中文字幕在线 | 亚洲高清视频在线观看 | 国产成人综合一区二区三区 | 99久久国产免费 | 毛片日韩 | 成人一区二区三区在线 | 亚洲综合婷婷 | 精品日韩欧美一区二区 | 亚洲精品在线免费看 | 精品日韩一区二区 | 蜜桃精品视频在线 | 国产91丝袜在线播放 | 久久久久久国产 | 欧美综合一区二区 | 91色视频在线观看 | 91一区| 香蕉视频91| 国产欧美日韩久久久 | 青青草一区二区 | 国产在线色 | 国产传媒在线观看 | 国产高清一区二区 | 欧美日韩在线视频一区 | 特级黄色毛片 | 91精品久久久久久久 | 免费看黄视频网站 |