JUC並行系列(十):超全透徹ReadWriteLock讀寫鎖(應用場景+程式碼範例+關鍵程式碼剖析)

2020-10-07 13:00:37

跑起一個程式,並不難;難的是,能讓程式跑多遠!—— 一顆剽悍的種子

在這裡插入圖片描述
JUC並行系列

JUC並行系列(一):什麼?聽說你搞混了並行和並行
JUC並行系列(二):詳解Condition實現精準通知喚醒
JUC並行系列(三):面試問並行,一問鎖就懵(怒肝一篇透徹理解鎖,面試不慌)
JUC並行系列(四):【面試常問】多種方法解決ArrayList非執行緒安全,詳解CopyOnWriteArrayList
JUC並行系列(五):CopyOnWriteArraySet解決HashSet非執行緒安全
JUC並行系列(六):ConcurrentHashMap解決HashMap非執行緒安全
JUC並行系列(七):觸及Callable
JUC並行系列(八):並行程式設計常用輔助類CountDownLatch與CyclicBarrier
JUC並行系列(九):並行程式設計常用輔助類Semaphore

一、什麼是 ReadWriteLock讀寫鎖?

準確來說 ReadWriteLock讀寫鎖,應該是 讀/寫鎖,正如ReadWriteLock 讀/寫鎖也是分開來的,它管理著兩個為一組的鎖,一個是唯讀鎖,一個是寫鎖。

通過字面意思告訴你 ReadWriteLock讀/寫鎖 其實你是很難理解的,因為只有通過案例,只有通過一行行程式碼去驗證,你才能在深深的理解後有自己的總結。

有自己總結過後的知識才是你的,因為那是知識的結晶。

(技術之餘總會扯這些有的沒的,只想告訴你不要光看我的博文,學習這種活,也只有你理解透後,用自己所能理解的語言重塑,才是你自己的。

在這裡插入圖片描述
我們接下來用兩個小案例,首先是一個沒有使用鎖和使用ReadWriteLock讀寫鎖後的形式對比來學習。

二、手敲程式碼範例——並行下的快取

我們通過一個快取的小案例來,在沒有使用鎖的情況下,實現儲存和讀取的功能,並通過在多個執行緒的並行下。

public class Demo{
    public static void main(String[] args) {
        Cache cache = new Cache();
        //儲存
        for (int i = 1; i <= 3; i++) {
            final Integer index = i;
            new Thread(()->{
                cache.put(index+"",index);
            },String.valueOf(i)).start();
        }
        //讀取
        for (int i = 1; i <= 3; i++) {
            final Integer index = i;
            new Thread(()->{
                cache.get(index+"");
            },String.valueOf(i)).start();
        }
    }
}
class Cache{
    private Map<String,Integer> map = new HashMap<>();
    //儲存
    public void put(String key,Integer value){
        System.out.println("執行緒"+Thread.currentThread().getName() + "===儲存" + value);
        map.put(key,value);
        System.out.println("執行緒"+Thread.currentThread().getName() + "===已儲存");
    }
    //讀取
    public void get(String key){
        System.out.println("執行緒"+Thread.currentThread().getName() + "===讀取");
        map.get(key);
        System.out.println("執行緒"+Thread.currentThread().getName() + "===已讀取");
    }
}

三、執行結果

在這裡插入圖片描述

四、分析結果

觀察上面的程式碼所執行的結果會發現兩點:

  1. 當執行緒1在儲存時被其他執行緒插入。

在這裡插入圖片描述

  1. 每次執行的結果都跟上一次不一致。

在這裡插入圖片描述

五、關鍵程式碼剖析:讀/寫鎖裡的獨佔鎖與共用鎖

如果你理解了上面為什麼ReadWriteLock讀/寫鎖是分成兩個為一組的讀/寫,其實你在實際運用中也就能更深刻透徹。

我們快取 put 是儲存就是寫入,可以用 writeLock寫鎖。

而讀取 get 可以用讀鎖。

可以看到不會像synchronized直接鎖或lock,它們更加細緻,也更具有針對性的分而治之。

這裡的寫鎖可以說是共用鎖,獨佔鎖又稱排他鎖。

共用鎖(寫鎖):一次只能一個執行緒持有。

readWriteLock.writeLock().lock();

共用鎖(讀鎖):多個執行緒可以同時持有。

readWriteLock.readLock().lock();

讀/寫鎖使用後都需要分別關閉,跟Lock最後也需要手動關閉是一樣一樣的。

readWriteLock.writeLock().unlock();
readWriteLock.readLock().unlock();

六、使用ReadWriteLock讀/寫鎖解決快取並行問題

public class Demo{
    public static void main(String[] args) {
        Cache cache = new Cache();
        //儲存
        for (int i = 1; i <= 3; i++) {
            final Integer index = i;
            new Thread(()->{
                cache.put(index+"",index);
            },String.valueOf(i)).start();
        }
        //讀取
        for (int i = 1; i <= 3; i++) {
            final Integer index = i;
            new Thread(()->{
                cache.get(index+"");
            },String.valueOf(i)).start();
        }
    }
}
class Cache{
    private Map<String,Integer> map = new HashMap<>();
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    //儲存
    public void put(String key,Integer value){
        readWriteLock.writeLock().lock();
        try {
            System.out.println("執行緒"+Thread.currentThread().getName() + "===儲存" + value);
            map.put(key,value);
            System.out.println("執行緒"+Thread.currentThread().getName() + "===已儲存");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }
    //讀取
    public void get(String key){
        readWriteLock.readLock().lock();
        try {
            System.out.println("執行緒"+Thread.currentThread().getName() + "===讀取");
            map.get(key);
            System.out.println("執行緒"+Thread.currentThread().getName() + "===已讀取");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.readLock().unlock();
        }
    }
}

七、執行結果(漂亮)

可以看到和上面沒有加鎖的執行結果,加了ReadWriteLock讀寫鎖 後很直觀的結果。在多執行緒並行下儲存的時候會有秩序的執行。

在這裡插入圖片描述

八、應用場景

ReadWriteLock讀/寫鎖 的不僅概念或者案例的對比和驗證中可以很直觀的發現其特性。

使用 ReadWriteLock讀/寫鎖 時, 適用少數執行緒修改操作,但可以有大量執行緒讀取。

所以可以運用的場景有很多很多,例如,CSDN裡的每一篇博文底下都有 評論與回覆,可以看做是 寫入操作,它是不頻繁;但是,讀取操作,也就是 瀏覽,是非常頻繁的,這種型別的應用場景可以使用ReadWriteLock讀/寫鎖

九、最後

最後的最後,為了更好的閱讀體驗,我把想說的話都放在了下面,嘿嘿。

我是一顆剽悍的種子 把我會的,認真的分享 是我寫部落格一直不變的信條。
如果你能看到這篇博文,說明咱們還是很有緣的;希望能帶給你一些許幫助,創作的不易,
把我文章的知識帶走,你的三連留下,點贊,評論,關注,是我最大的動力。