(9)讀寫鎖ReentrantReadWriteLock解析

一、 ReentrantReadWriteLock簡介

    ReentrantReadWriteLock是在AQS的基礎上實現的一個可重入鎖。它的內部維護了一把讀鎖和一把寫鎖,讀鎖是共享鎖,寫鎖是排他鎖。這樣就保證了寫數據時的線程安全性,又保證了讀數據時的多線程併發,比較適合讀取數據較多而寫數據較少的併發場景。

    寫一個例子演示ReentrantReadWriteLock的使用。

class RWData<T>{
    private T data ;
    private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(true);
    private ReentrantReadWriteLock.ReadLock readLock = rwl.readLock();
    private ReentrantReadWriteLock.WriteLock writeLock = rwl.writeLock();
    public RWData(){
    }

    public void put(T t){
        try{
            System.out.println("Before write lock");
            writeLock.lock();
            data = t;
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            writeLock.unlock();
            System.out.println("After write unlock");
        }
    }

    public T get() {
        try{
            readLock.lock();
            return data;
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            readLock.unlock();
        }
        return null;
    }
}



public class TestRWLock {
    public static void main(String[] args) {
        RWData<Integer> queue = new RWData<>();
        for (int i = 0; i < 10; i++){
            new Thread(()->{
                while(true){
                    Integer data = queue.get();
                    if (null != data)
                        System.out.println(Thread.currentThread().getName() + "gets data " + data.intValue());
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
             }).start();
        }
        
    new Thread(()->{
            while(true){
                int data = new Random().nextInt(1000);
                queue.put(data);
                System.out.println(Thread.currentThread().getName() + "puts data " + data);
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

  運行結果

   寫的時候其他的線程都需要等待,當讀的時候所有的線程可以同時讀。

   寫鎖是獨佔的,寫時不能有其他線程寫也不能讀;所有的獨鎖都釋放完之前也不能加寫鎖。這將在代碼中有體現。

 

二、ReentrantReadWriteLock源碼解析

    一個讀寫鎖,需要維護3個數據:

  •     寫鎖的重入次數
  •     讀鎖的個數(讀鎖有多個)
  •     讀鎖的重入次數

Sync繼承AQS的state,高16位存讀的,低16位存寫的,Sync類的readHolds存儲了每一個讀鎖的重入次數。

通過getState與setState來獲取和保存。

    ReentrantReadWriteLock內部有一個ReadLock 類和一個WriteLock類,各包含了一個Sync子類實例負責具體幹活。Sync類extends AQS實現了大多數功能,但它是abstract類,需要實例化子類FairSync和NonfairSync來幹活。它們分別實現了公平方式和非公平方式。

    創建ReentrantReadWriteLock對象時要選擇公平還是非公平,公平就用FairSync,非公平就用NonfairSync,默認非公平。

 

  •    ReadLock

   ReadLock的lock調用了內部的sync實例的acquireShared()

 acquireShared調用了ReadLock中存的sync實例的tryAcquireShared()

  unlock則調用了 --> sync.releaseShared()  --> Sync子類實例的 tryReleaseShared。    

 

  • WriteLock

  writeLock的lock調用了--> sync.acquire()  --> Sync子類實例的tryAcquire

  unlock調用了--> sync.acquire()  --> Sync子類實例的tryAcquire

這些代碼的意義與上一節 (7)AbstractQueuedSynchronizer和ReentrantLock—— 可重入鎖的實現  基本一致,無需贅述。

 

 

     writerShouldBlock看是否需要使其他的線程阻塞。

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