ReadWriteLock鎖的應用

對於 Lock 鎖來說,如果要實現 「一寫多讀」 的並髮狀態(即允許同時讀,不允許同時寫),需要對 「寫操作」 加鎖,對 「讀操作」 不作要求即可。但是如果對於 「讀」 操作下,有 「寫操作」 接入的話,對於當前的 「讀操作」 可能會產生 「幻讀」 的現象。所以對於要實現 「一寫多讀」 的情況下,應推薦使用 ReadWriteLock 鎖。

ReadWriteLock 是與 Lock 平級的一個 JUC 包下的介面

它唯一的實現類是 ReentrantReadWriteLock 類,一個 ReentrantReadWriteLock 對象維護了一對關聯的locks ,一個用於只讀操作,一個用於寫入。

簡單來說:

  • 它維護了兩個鎖,一個叫 「讀鎖」,一個叫 「寫鎖」。
  • 為了允許 「一寫多讀」 的操作,按理上,「寫鎖」 一旦上鎖,不能再被獲取;而為了保證能同時讀數據,「讀鎖」 若上鎖,想獲取 「讀鎖」 的執行緒仍然可以執行讀操作,而為了防止 「讀操作」 執行時有 「寫操作」 的接入,應該要防止 「寫鎖」 被獲取。

下面通過 4 個不同順序的讀寫實例來演示一遍(程式碼貼在最後面)

1、一個執行緒已獲取 「讀鎖」 狀態下,另一個執行緒嘗試獲取 「寫鎖」 。

在 「讀鎖」 已被獲取的情況下,「寫鎖」 不能被獲取。(即便是在多個執行緒都獲取 「讀鎖」,「寫鎖」 必須在所有 「讀操作」 結束後才能被獲取,可自行測試)

2、一個執行緒已獲取 「寫鎖」 狀態下,另一個執行緒嘗試獲取 「讀鎖」 。

在 「寫鎖」 已被獲取的情況下,「讀鎖」 不能被獲取。

3、一個執行緒已獲取 「讀鎖」 狀態下,另一個執行緒嘗試獲取 「讀鎖」 。

在 「讀鎖」 已被獲取的情況下,「讀鎖」 還能再被獲取,類似於 Semaphore 輔助類。

4、一個執行緒已獲取 「寫鎖」 狀態下,另一個執行緒嘗試獲取 「寫鎖」 。

在 「寫鎖」 已被獲取的情況下,「寫鎖」 不能再被獲取。

總程式碼

package ReadWriteLock;

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockDemo {

    private final static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    public static void main(String[] args) throws InterruptedException {
        writeAndWriteTest();
    }

    public static void readAndWriteTest() throws InterruptedException {
        //第一個執行緒先獲取寫鎖,另一個執行緒再獲取讀鎖
        new Thread(() -> {
            readWriteLock.writeLock().lock();
            System.out.println("寫鎖已獲取");
            try {
                Thread.sleep(8000);
                readWriteLock.writeLock().unlock();
                System.out.println("已關閉寫鎖");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        Thread.sleep(3000);
        System.out.println("另一個執行緒嘗試獲取讀鎖");
        new Thread(() -> {
            readWriteLock.readLock().lock();
            System.out.println("另一個執行緒的讀鎖已獲取");
        }).start();
    }

    public static void writeAndReadTest() throws InterruptedException {
        //第一個執行緒先獲取讀鎖,另一個執行緒再獲取寫鎖
        new Thread(() -> {
            readWriteLock.readLock().lock();
            System.out.println("讀鎖已獲取");
            try {
                Thread.sleep(8000);
                readWriteLock.readLock().unlock();
                System.out.println("已關閉讀鎖");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        Thread.sleep(3000);
        System.out.println("另一個執行緒嘗試獲取寫鎖");
        new Thread(() -> {
            readWriteLock.writeLock().lock();
            System.out.println("另一個執行緒的寫鎖已獲取");
        }).start();
    }

    public static void readAndReadTest() throws InterruptedException {
        //一個執行緒已獲讀鎖,另一個執行緒再獲取讀鎖
        new Thread(() -> {
            readWriteLock.readLock().lock();
            System.out.println("讀鎖已獲取");
            try {
                Thread.sleep(8000);
                readWriteLock.readLock().unlock();
                System.out.println("已關閉讀鎖");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        Thread.sleep(3000);
        System.out.println("另一個執行緒嘗試獲取讀鎖");
        new Thread(() -> {
            readWriteLock.readLock().lock();
            System.out.println("另一個執行緒的讀鎖已獲取");
        }).start();
    }

    public static void writeAndWriteTest() throws InterruptedException {
        //一個執行緒已獲寫鎖,另一個執行緒再獲取寫鎖
        new Thread(() -> {
            readWriteLock.writeLock().lock();
            System.out.println("寫鎖已獲取");
            try {
                Thread.sleep(8000);
                readWriteLock.writeLock().unlock();
                System.out.println("已關閉寫鎖");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        Thread.sleep(3000);
        System.out.println("另一個執行緒嘗試獲取寫鎖");
        new Thread(() -> {
            readWriteLock.writeLock().lock();
            System.out.println("另一個執行緒的寫鎖已獲取");
        }).start();
    }
}

總結

ReentrantReadWriteLock 中的 「寫鎖」 類似於 Lock 鎖,而 「讀鎖」 類似於 Semaphore 訊號量機制。「寫鎖」 保證臨界資源能被唯一佔用,解決了 「寫寫同步問題」。而當且僅當 「讀鎖」 隊列為空時,「寫鎖」 才能被獲取。且 「寫鎖」 和 「讀鎖」 在同一時刻不能同時被持有,解決了 「讀寫同步問題」,且它還能保證能夠 「同時讀」 的特性。

Tags: