成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

[Java并發(fā)-10] ReadWriteLock:快速實(shí)現(xiàn)一個(gè)完備的緩存

nevermind / 2830人閱讀

摘要:此時(shí)線程和會(huì)再有一個(gè)線程能夠獲取寫鎖,假設(shè)是,如果不采用再次驗(yàn)證的方式,此時(shí)會(huì)再次查詢數(shù)據(jù)庫。而實(shí)際上線程已經(jīng)把緩存的值設(shè)置好了,完全沒有必要再次查詢數(shù)據(jù)庫。

大家知道了Java中使用管程同步原語,理論上可以解決所有的并發(fā)問題。那 Java SDK 并發(fā)包里為什么還有很多其他的工具類呢?原因很簡單:分場景優(yōu)化性能,提升易用性

今天我們就介紹一種非常普遍的并發(fā)場景:讀多寫少場景。實(shí)際工作中,為了優(yōu)化性能,我們經(jīng)常會(huì)使用緩存,例如緩存元數(shù)據(jù)、緩存基礎(chǔ)數(shù)據(jù)等,這就是一種典型的讀多寫少應(yīng)用場景。緩存之所以能提升性能,一個(gè)重要的條件就是緩存的數(shù)據(jù)一定是讀多寫少的.

針對讀多寫少這種并發(fā)場景,Java SDK 并發(fā)包提供了讀寫鎖——ReadWriteLock,非常容易使用,并且性能很好。

什么是讀寫鎖

讀寫鎖,并不是 Java 語言特有的,而是一個(gè)廣為使用的通用技術(shù),所有的讀寫鎖都遵守以下三條基本原則:

允許多個(gè)線程同時(shí)讀共享變量;

只允許一個(gè)線程寫共享變量;

如果一個(gè)寫線程正在執(zhí)行寫操作,此時(shí)禁止讀線程讀共享變量。

讀寫鎖與互斥鎖的一個(gè)重要區(qū)別就是讀寫鎖允許多個(gè)線程同時(shí)讀共享變量,而互斥鎖是不允許的,這是讀寫鎖在讀多寫少場景下性能優(yōu)于互斥鎖的關(guān)鍵。但讀寫鎖的寫操作是互斥的,當(dāng)一個(gè)線程在寫共享變量的時(shí)候,是不允許其他線程執(zhí)行讀操作和寫操作的。

快速實(shí)現(xiàn)一個(gè)緩存

在下面的代碼中,我們聲明了一個(gè) Cache 類,其中類型參數(shù) K 代表緩存里 key 的類型,V 代表緩存里 value 的類型。緩存的數(shù)據(jù)保存在 Cache 類內(nèi)部的 HashMap 里面,HashMap 不是線程安全的,這里我們使用讀寫鎖 ReadWriteLock 來保證其線程安全。ReadWriteLock 是一個(gè)接口,它的實(shí)現(xiàn)類是 ReentrantReadWriteLock,通過名字你應(yīng)該就能判斷出來,它是支持可重入的。下面我們通過 rwl 創(chuàng)建了一把讀鎖和一把寫鎖。

Cache 這個(gè)工具類,我們提供了兩個(gè)方法,一個(gè)是讀緩存方法 get(),另一個(gè)是寫緩存方法 put()。讀緩存需要用到讀鎖,讀鎖的使用和前面我們介紹的 Lock 的使用是相同的,都是 try{}finally{}這個(gè)編程范式。寫緩存則需要用到寫鎖,寫鎖的使用和讀鎖是類似的。

class Cache {
  final Map m =
    new HashMap<>();
  final ReadWriteLock rwl =
    new ReentrantReadWriteLock();
  // 讀鎖
  final Lock r = rwl.readLock();
  // 寫鎖
  final Lock w = rwl.writeLock();
  // 讀緩存
  V get(K key) {
    r.lock();
    try { return m.get(key); }
    finally { r.unlock(); }
  }
  // 寫緩存
  V put(String key, Data v) {
    w.lock();
    try { return m.put(key, v); }
    finally { w.unlock(); }
  }
}
實(shí)現(xiàn)緩存的按需加載

設(shè)計(jì)封裝緩存類時(shí),我們需要在當(dāng)應(yīng)用查詢緩存,并且數(shù)據(jù)不在緩存里的時(shí)候,觸發(fā)加載源頭相關(guān)數(shù)據(jù)進(jìn)緩存的操作,這也是我們需要實(shí)現(xiàn)的最基本的功能。下面看下利用 ReadWriteLock 來實(shí)現(xiàn)緩存的按需加載。

這里我們假設(shè)緩存的源頭是數(shù)據(jù)庫。需要注意的是,如果緩存中沒有緩存目標(biāo)對象,那么就需要從數(shù)據(jù)庫中加載,然后寫入緩存,寫緩存需要用到寫鎖,所以在代碼中的⑤處,我們調(diào)用了w.lock() 來獲取寫鎖。

另外,還需要注意的是,在獲取寫鎖之后,我們并沒有直接去查詢數(shù)據(jù)庫,而是在代碼⑥⑦處,重新驗(yàn)證了一次緩存中是否存在,再次驗(yàn)證如果還是不存在,我們才去查詢數(shù)據(jù)庫并更新本地緩存。為什么我們要再次驗(yàn)證呢?

class Cache {
  final Map m =
    new HashMap<>();
  final ReadWriteLock rwl = 
    new ReentrantReadWriteLock();
  final Lock r = rwl.readLock();
  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)證
      // 其他線程可能已經(jīng)查詢過數(shù)據(jù)庫
      v = m.get(key); ⑥
      if(v == null){  ⑦
        // 查詢數(shù)據(jù)庫
        v= 省略代碼無數(shù)
        m.put(key, v);
      }
    } finally{
      w.unlock();
    }
    return v; 
  }
}

原因是在高并發(fā)的場景下,有可能會(huì)有多線程競爭寫鎖。假設(shè)緩存是空的,沒有緩存任何東西,如果此時(shí)有三個(gè)線程 T1、T2 和 T3 同時(shí)調(diào)用 get() 方法,并且參數(shù) key 也是相同的。那么它們會(huì)同時(shí)執(zhí)行到代碼⑤處,但此時(shí)只有一個(gè)線程能夠獲得寫鎖,假設(shè)是線程 T1,線程 T1 獲取寫鎖之后查詢數(shù)據(jù)庫并更新緩存,最終釋放寫鎖。此時(shí)線程 T2 和 T3 會(huì)再有一個(gè)線程能夠獲取寫鎖,假設(shè)是 T2,如果不采用再次驗(yàn)證的方式,此時(shí) T2 會(huì)再次查詢數(shù)據(jù)庫。T2 釋放寫鎖之后,T3 也會(huì)再次查詢一次數(shù)據(jù)庫。而實(shí)際上線程 T1 已經(jīng)把緩存的值設(shè)置好了,T2、T3 完全沒有必要再次查詢數(shù)據(jù)庫。所以,再次驗(yàn)證的方式,能夠避免高并發(fā)場景下重復(fù)查詢數(shù)據(jù)的問題。

讀寫鎖的升級

上面按需加載的示例代碼中,在①處獲取讀鎖,在③處釋放讀鎖,那是否可以在②處的下面增加驗(yàn)證緩存并更新緩存的邏輯呢?詳細(xì)的代碼如下。

// 讀緩存
r.lock();         ①
try {
  v = m.get(key); ②
  if (v == null) {
    w.lock();
    try {
      // 再次驗(yàn)證并更新緩存
      // 省略詳細(xì)代碼
    } finally{
      w.unlock();
    }
  }
} finally{
  r.unlock();     ③
}

這樣看上去好像是沒有問題的,先是獲取讀鎖,然后再升級為寫鎖,對此還有個(gè)專業(yè)的名字,叫鎖的升級??上?ReadWriteLock 并不支持這種升級。在上面的代碼示例中,讀鎖還沒有釋放,此時(shí)獲取寫鎖,會(huì)導(dǎo)致寫鎖永久等待,最終導(dǎo)致相關(guān)線程都被阻塞,永遠(yuǎn)也沒有機(jī)會(huì)被喚醒。

小結(jié)

讀寫鎖類似于 ReentrantLock,也支持公平模式和非公平模式。讀鎖和寫鎖都實(shí)現(xiàn)了 java.util.concurrent.locks.Lock 接口,所以除了支持 lock() 方法外,tryLock()、lockInterruptibly() 等方法也都是支持的。但是有一點(diǎn)需要注意,那就是只有寫鎖支持條件變量,讀鎖是不支持條件變量的,讀鎖調(diào)用 newCondition() 會(huì)拋出 UnsupportedOperationException 異常。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/77659.html

相關(guān)文章

  • Java多線程—ReentrantReadWriteLock源碼閱讀

    摘要:不同的是它還多了內(nèi)部類和內(nèi)部類,以及讀寫對應(yīng)的成員變量和方法。另外是給和內(nèi)部類使用的。內(nèi)部類前面說到的操作是分配到里面執(zhí)行的。他們都是接口的實(shí)現(xiàn),所以其實(shí)最像應(yīng)該是這個(gè)兩個(gè)內(nèi)部類。而且大體上也沒什么差異,也是用的內(nèi)部類。 之前講了《AQS源碼閱讀》和《ReentrantLock源碼閱讀》,本次將延續(xù)閱讀下ReentrantReadWriteLock,建議沒看過之前兩篇文章的,先大概了解...

    Ververica 評論0 收藏0
  • Java 8 并發(fā)教程:同步和鎖

    摘要:在接下來的分鐘,你將會(huì)學(xué)會(huì)如何通過同步關(guān)鍵字,鎖和信號量來同步訪問共享可變變量。所以在使用樂觀鎖時(shí),你需要每次在訪問任何共享可變變量之后都要檢查鎖,來確保讀鎖仍然有效。 原文:Java 8 Concurrency Tutorial: Synchronization and Locks譯者:飛龍 協(xié)議:CC BY-NC-SA 4.0 歡迎閱讀我的Java8并發(fā)教程的第二部分。這份指南將...

    wyk1184 評論0 收藏0
  • Java 8 并發(fā):同步和鎖

    摘要:可重入意味著鎖被綁定到當(dāng)前線程,線程可以安全地多次獲取相同的鎖,而不會(huì)發(fā)生死鎖例如同步方法在同一對象上調(diào)用另一個(gè)同步方法。寫入鎖釋放后,兩個(gè)任務(wù)并行執(zhí)行,它們不必等待對方是否完成,因?yàn)橹灰獩]有線程持有寫入鎖,它們就可以同時(shí)持有讀取鎖。 原文地址: Java 8 Concurrency Tutorial: Synchronization and Locks 為了簡單起見,本教程的示例代...

    andycall 評論0 收藏0
  • Java 多線程

    摘要:當(dāng)一個(gè)程序運(yùn)行時(shí),內(nèi)部可能包含了多個(gè)順序執(zhí)行流,每個(gè)順序執(zhí)行流就是一個(gè)線程所有運(yùn)行中的任務(wù)通常對應(yīng)一個(gè)進(jìn)程。線程也被稱作輕量級進(jìn)程,線程是進(jìn)程的執(zhí)行單元。在線程的生命周期中,它要經(jīng)過新 線程概述 線程和進(jìn)程 幾乎所有的操作系統(tǒng)都支持同時(shí)運(yùn)行多個(gè)任務(wù),一個(gè)任務(wù)通常就是一個(gè)程序,每個(gè)運(yùn)行中的程序就是一個(gè)進(jìn)程。當(dāng)一個(gè)程序運(yùn)行時(shí),內(nèi)部可能包含了多個(gè)順序執(zhí)行流,每個(gè)順序執(zhí)行流就是一個(gè)線程 所有運(yùn)...

    zorro 評論0 收藏0
  • 如何使用 Java8 實(shí)現(xiàn)觀察者模式?(下)

    摘要:線程安全的實(shí)現(xiàn)前面章節(jié)介紹了在現(xiàn)代環(huán)境下的實(shí)現(xiàn)觀察者模式,雖然簡單但很完整,但這一實(shí)現(xiàn)忽略了一個(gè)關(guān)鍵性問題線程安全。截止目前實(shí)現(xiàn)了線程安全,在接下來的章節(jié)中將介紹提取主題的邏輯并將其類封裝為可重復(fù)代碼單元的方式優(yōu)缺點(diǎn)。 【編者按】本文作者是 BAE 系統(tǒng)公司的軟件工程師 Justin Albano。在本篇文章中,作者通過在 Java8 環(huán)境下實(shí)現(xiàn)觀察者模式的實(shí)例,進(jìn)一步介紹了什么是觀察...

    afishhhhh 評論0 收藏0

發(fā)表評論

0條評論

最新活動(dòng)
閱讀需要支付1元查看
<