亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

讀寫鎖 ReentrantReadWriteLock

1. 前言

本節內容主要是對 Java 讀寫鎖 ReentrantReadWriteLock 進行講解,本節內容幾乎全部為重點知識,需要學習者對 ReentrantReadWriteLock 進行理解和掌握。本節內容的知識點如下:

  • ReentrantReadWriteLock 簡單介紹,對 ReentrantReadWriteLock 進行一個總體的概括;
  • ReentrantReadWriteLock 的類結構,從 Java 層面了解 ReentrantReadWriteLock;
  • ReentrantReadWriteLock 的特點,相比于上兩點知識,該知識點可視為重點;
  • ReentrantReadWriteLock 讀鎖共享的性質驗證,為本節核心內容之一;
  • ReentrantReadWriteLock 讀寫互斥的性質驗證,為本節核心內容之一。

ReentrantReadWriteLock 在 Java 的鎖當中也占據著十分重要的地位,在并發編程中使用頻率也是非常的高,一定要對本節內容進行細致的學習和掌握。

2. ReentrantReadWriteLock 介紹

JDK 提供了 ReentrantReadWriteLock 讀寫鎖,使用它可以加快效率,在某些不需要操作實例變量的方法中,完全可以使用讀寫鎖 ReemtrantReadWriteLock 來提升該方法的運行速度。

定義:讀寫鎖表示有兩個鎖,一個是讀操作相關的鎖,也稱為共享鎖;另一個是寫操作相關的鎖,也叫排他鎖。

定義解讀:也就是多個讀鎖之間不互斥,讀鎖與寫鎖互斥、寫鎖與寫鎖互斥。在沒有線程 Thread 進行寫入操作時,進行讀取操作的多個 Thread 都可以獲取讀鎖,而進行寫入操作的 Thread 只有在獲取寫鎖后才能進行寫入操作。即多個 Thread 可以同時進行讀取操作,但是同一時刻只允許一個 Thread 進行寫入操作。

3. ReentrantReadWriteLock 的類結構

ReentrantReadWriteLock 是接口 ReadWriteLock 的子類實現,通過 JDK 的代碼可以看出這一實現關系。

public class ReentrantReadWriteLock
        implements ReadWriteLock, java.io.Serializable{}

我們再來看下接口 ReadWriteLock,該接口只定義了兩個方法:

public interface ReadWriteLock {
    Lock readLock();
    Lock writeLock();
}

通過調用相應方法獲取讀鎖或寫鎖,可以如同使用 Lock 接口一樣使用。

4. ReentrantReadWriteLock 的特點

性質 1 :可重入性。

ReentrantReadWriteLock 與 ReentrantLock 以及 synchronized 一樣,都是可重入性鎖,這里不會再多加贅述所得可重入性質,之前已經做過詳細的講解。

性質 2 :讀寫分離。

我們知道,對于一個數據,不管是幾個線程同時讀都不會出現任何問題,但是寫就不一樣了,幾個線程對同一個數據進行更改就可能會出現數據不一致的問題,因此想出了一個方法就是對數據加鎖,這時候出現了一個問題:

線程寫數據的時候加鎖是為了確保數據的準確性,但是線程讀數據的時候再加鎖就會大大降低效率,這時候怎么辦呢?那就對寫數據和讀數據分開,加上兩把不同的鎖,不僅保證了正確性,還能提高效率。

性質 3 :可以鎖降級,寫鎖降級為讀鎖。

線程獲取寫入鎖后可以獲取讀取鎖,然后釋放寫入鎖,這樣就從寫入鎖變成了讀取鎖,從而實現鎖降級的特性。

圖片描述

性質 4 :不可鎖升級。

線程獲取讀鎖是不能直接升級為寫入鎖的。需要釋放所有讀取鎖,才可獲取寫鎖。

圖片描述

5. ReentrantReadWriteLock 讀鎖共享

我們之前說過,ReentrantReadWriteLock 之所以優秀,是因為讀鎖與寫鎖是分離的,當所有的線程都為讀操作時,不會造成線程之間的互相阻塞,提升了效率,那么接下來,我們通過代碼實例進行學習。

場景設計

  • 創建三個線程,線程名稱分別為 t1,t2,t3,線程實現方式自行選擇;
  • 三個線程同時運行獲取讀鎖,讀鎖成功后打印線程名和獲取結果,并沉睡 2000 毫秒,便于觀察其他線程是否可共享讀鎖;
  • finally 模塊中釋放鎖并打印線程名和釋放結果;
  • 運行程序,觀察結果。

結果預期:三條線程能同時獲取鎖,因為讀鎖共享。

實例

public class DemoTest {
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();// 讀寫鎖
    private int i;
    public String readI() {
        try {
            lock.readLock().lock();// 占用讀鎖
            System.out.println("threadName -> " + Thread.currentThread().getName() + " 占用讀鎖,i->" + i);
            Thread.sleep(2000);
        } catch (InterruptedException e) {

        } finally {
            System.out.println("threadName -> " + Thread.currentThread().getName() + " 釋放讀鎖,i->" + i);
            lock.readLock().unlock();// 釋放讀鎖
        }
        return i + "";
    }

    public static void main(String[] args) {
        final DemoTest demo1 = new DemoTest();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                demo1.readI();
            }
        };
        new Thread(runnable, "t1"). start();
        new Thread(runnable, "t2"). start();
        new Thread(runnable, "t3"). start();
    }
}

結果驗證

threadName -> t1 占用讀鎖,i->0
threadName -> t2 占用讀鎖,i->0
threadName -> t3 占用讀鎖,i->0
threadName -> t1 釋放讀鎖,i->0
threadName -> t3 釋放讀鎖,i->0
threadName -> t2 釋放讀鎖,i->0

結果分析:從結果來看,t1,t2,t3 均在同一時間獲取了鎖,證明了讀鎖共享的性質。

6. ReentrantReadWriteLock 讀寫互斥

當共享變量有寫操作時,必須要對資源進行加鎖,此時如果一個線程正在進行讀操作,那么寫操作的線程需要等待。同理,如果一個線程正在寫操作,讀操作的線程需要等待。

場景設計:細節操作不詳細闡述,看示例代碼即可。

  • 創建兩個線程,線程名稱分別為 t1,t2;
  • 線程 t1 進行讀操作,獲取到讀鎖之后,沉睡 5000 毫秒;
  • 線程 t2 進行寫操作;
  • 開啟 t1,1000 毫秒后開啟 t2 線程;
  • 運行程序,觀察結果。

結果預期:線程 t1 獲取了讀鎖,在沉睡的 5000 毫秒中,線程 t2 只能等待,不能獲取到鎖,因為讀寫互斥。

實例

public class DemoTest {
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();// 讀寫鎖
    private int i;
    public String readI() {
        try {
            lock.readLock().lock();// 占用讀鎖
            System.out.println("threadName -> " + Thread.currentThread().getName() + " 占用讀鎖,i->" + i);
            Thread.sleep(5000);
        } catch (InterruptedException e) {
        } finally {
            System.out.println("threadName -> " + Thread.currentThread().getName() + " 釋放讀鎖,i->" + i);
            lock.readLock().unlock();// 釋放讀鎖
        }
        return i + "";
    }

    public void addI() {
        try {
            lock.writeLock().lock();// 占用寫鎖
            System.out.println("threadName -> " + Thread.currentThread().getName() + " 占用寫鎖,i->" + i);
            i++;
        } finally {
            System.out.println("threadName -> " + Thread.currentThread().getName() + " 釋放寫鎖,i->" + i);
            lock.writeLock().unlock();// 釋放寫鎖
        }
    }

    public static void main(String[] args) throws InterruptedException {
        final DemoTest demo1 = new DemoTest();
        new Thread(new Runnable() {
            @Override
            public void run() {
                demo1.readI();
            }
        }, "t1"). start();
        Thread.sleep(1000);
        new Thread(new Runnable() {
            @Override
            public void run() {
                demo1.addI();
            }
        }, "t2"). start();
    }
}

結果驗證

threadName -> t1 占用讀鎖,i->0
threadName -> t1 釋放讀鎖,i->0
threadName -> t2 占用寫鎖,i->0
threadName -> t2 釋放寫鎖,i->1

結果解析:驗證成功,在線程 t1 沉睡的過程中,寫鎖 t2 線程無法獲取鎖,因為鎖已經被讀操作 t1 線程占據了。

7. 小結

本節內容只要是對讀寫鎖 ReentrantReadWriteLock 進行的比較細致的講解,對于本節的內容幾乎通篇為重點內容。

其中核心知識點為讀鎖共享和讀寫互斥的驗證,所有的知識點都是圍繞這兩個話題進行講解的,有興趣的同學可以根據實例代碼進行寫鎖互斥的驗證。唯一不同的地方就是創建兩個寫線程進行寫鎖的獲取。

掌握本節知識點,有助于我們在特定的場景下對讀寫鎖進行應用。