欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java多線程讀寫鎖ReentrantReadWriteLock類詳解

 更新時間:2021年12月16日 17:10:23   作者:讓我發(fā)會呆  
本文詳細講解了Java多線程讀寫鎖ReentrantReadWriteLock類,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

真實的多線程業(yè)務(wù)開發(fā)中,最常用到的邏輯就是數(shù)據(jù)的讀寫,ReentrantLock雖然具有完全互斥排他的效果(即同一時間只有一個線程正在執(zhí)行l(wèi)ock后面的任務(wù)),這樣做雖然保證了實例變量的線程安全性,但效率卻是非常低下的。所以在JDK中提供了一種讀寫鎖ReentrantReadWriteLock類,使用它可以加快運行效率。

讀寫鎖表示兩個鎖,一個是讀操作相關(guān)的鎖,稱為共享鎖;另一個是寫操作相關(guān)的鎖,稱為排他鎖。

下面我們通過代碼去驗證下讀寫鎖之間的互斥性

ReentrantReadWriteLock

讀讀共享

首先創(chuàng)建一個對象,分別定義一個加讀鎖方法和一個加寫鎖的方法,

public class MyDomain3 {
 
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
 
    public void testReadLock() {
        try {
            lock.readLock().lock();
            System.out.println(System.currentTimeMillis() + " 獲取讀鎖");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.readLock().unlock();
        }
    }
 
    public void testWriteLock() {
        try {
            lock.writeLock().lock();
            System.out.println(System.currentTimeMillis() + " 獲取寫鎖");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.writeLock().unlock();
        }
    }
 
}

創(chuàng)建線程類1 調(diào)用加讀鎖方法

public class Mythread3_1 extends Thread {
 
    private MyDomain3 myDomain3;
 
    public Mythread3_1(MyDomain3 myDomain3) {
        this.myDomain3 = myDomain3;
    }
 
    @Override
    public void run() {
        myDomain3.testReadLock();
    }
}

@Test
    public void test3() throws InterruptedException {
        MyDomain3 myDomain3 = new MyDomain3();
        Mythread3_1 readLock = new Mythread3_1(myDomain3);
        Mythread3_1 readLock2 = new Mythread3_1(myDomain3);
    readLock.start();
    readLock2.start();
 
        Thread.sleep(3000);
    }

執(zhí)行結(jié)果:

1639621812838 獲取讀鎖
1639621812839 獲取讀鎖

可以看出兩個讀鎖幾乎同時執(zhí)行,說明讀和讀之間是共享的,因為讀操作不會有線程安全問題。

寫寫互斥

創(chuàng)建線程類2,調(diào)用加寫鎖方法

public class Mythread3_2 extends Thread {
 
    private MyDomain3 myDomain3;
 
    public Mythread3_2(MyDomain3 myDomain3) {
        this.myDomain3 = myDomain3;
    }
 
    @Override
    public void run() {
        myDomain3.testWriteLock();
    }
}
@Test
    public void test3() throws InterruptedException {
        MyDomain3 myDomain3 = new MyDomain3();
        Mythread3_2 writeLock = new Mythread3_2(myDomain3);
        Mythread3_2 writeLock2 = new Mythread3_2(myDomain3);
 
        writeLock.start();
        writeLock2.start();
 
        Thread.sleep(3000);
    }

執(zhí)行結(jié)果:

1639622063226 獲取寫鎖
1639622064226 獲取寫鎖

從時間上看,間隔是1000ms即1s,說明寫鎖和寫鎖之間互斥。

讀寫互斥

再用線程1和線程2分別調(diào)用讀鎖與寫鎖

@Test
    public void test3() throws InterruptedException {
        MyDomain3 myDomain3 = new MyDomain3();
        Mythread3_1 readLock = new Mythread3_1(myDomain3);
        Mythread3_2 writeLock = new Mythread3_2(myDomain3);
 
    readLock.start();
        writeLock.start();
 
        Thread.sleep(3000);
    }

執(zhí)行結(jié)果:

1639622338402 獲取讀鎖
1639622339402 獲取寫鎖

從時間上看,間隔是1000ms即1s,和代碼里面是一致的,證明了讀和寫之間是互斥的。

注意一下,"讀和寫互斥"和"寫和讀互斥"是兩種不同的場景,但是證明方式和結(jié)論是一致的,所以就不證明了。

最終測試結(jié)果下:

  • 1、讀和讀之間不互斥,因為讀操作不會有線程安全問題
  • 2、寫和寫之間互斥,避免一個寫操作影響另外一個寫操作,引發(fā)線程安全問題
  • 3、讀和寫之間互斥,避免讀操作的時候?qū)懖僮餍薷牧藘?nèi)容,引發(fā)線程安全問題

總結(jié)起來就是,多個Thread可以同時進行讀取操作,但是同一時刻只允許一個Thread進行寫入操作。

源碼分析

讀寫鎖中的Sync也是同樣實現(xiàn)了AQS,回想ReentrantLock中自定義同步器的實現(xiàn),同步狀態(tài)表示鎖被一個線程重復(fù)獲取的次數(shù),而讀寫鎖的自定義同步器需要在同步狀態(tài)(一個整型變量)上維護多個讀線程和一個寫線程的狀態(tài),使得該狀態(tài)的設(shè)計成為讀寫鎖實現(xiàn)的關(guān)鍵。

讀寫鎖將變量切分成了兩個部分,高16位表示讀,低16位表示寫

當前同步狀態(tài)表示一個線程已經(jīng)獲取了寫鎖,且重進入了兩次,同時也連續(xù)獲取了兩次讀鎖。讀寫鎖是如何迅速確定讀和寫各自的狀態(tài)呢?

static final int SHARED_SHIFT   = 16;
static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
 
/** Returns the number of shared holds represented in count  */
static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
/** Returns the number of exclusive holds represented in count  */
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

其實是通過位運算。假設(shè)當前同步狀態(tài)值為c,寫狀態(tài)等于c & EXCLUSIVE_MASK (c&0x0000FFFF(將高16位全部抹去)),讀狀態(tài)等于c>>>16(無符號補0右移16位)。當寫狀態(tài)增加1時,等于c+1,當讀狀態(tài)增加1時,等于c+(1<<16),也就是c+0x00010000。

根據(jù)狀態(tài)的劃分能得出一個推論:c不等于0時,當寫狀態(tài)(c & 0x0000FFFF)等于0時,則讀狀態(tài)(c>>>16)大于0,即讀鎖已被獲取。

寫鎖的獲取與釋放

通過上面的測試,我們知道寫鎖是一個支持重入的排它鎖,看下源碼是如何實現(xiàn)寫鎖的獲取

protected final boolean tryAcquire(int acquires) {
            /*
             * Walkthrough:
             * 1. If read count nonzero or write count nonzero
             *    and owner is a different thread, fail.
             * 2. If count would saturate, fail. (This can only
             *    happen if count is already nonzero.)
             * 3. Otherwise, this thread is eligible for lock if
             *    it is either a reentrant acquire or
             *    queue policy allows it. If so, update state
             *    and set owner.
             */
            Thread current = Thread.currentThread();
            int c = getState();
            int w = exclusiveCount(c);
            if (c != 0) {
                // (Note: if c != 0 and w == 0 then shared count != 0)
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                if (w + exclusiveCount(acquires) > MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                // Reentrant acquire
                setState(c + acquires);
                return true;
            }
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
                return false;
            setExclusiveOwnerThread(current);
            return true;
        }

第3行到第11行,簡單說了下整個方法的實現(xiàn)邏輯,這里要夸一下,這段注釋就很容易的讓人知道代碼的功能。下面我們分析一下,第13到第15行,分別拿到了當前線程對象current,lock的加鎖狀態(tài)值c 以及寫鎖的值w,c!=0 表明 當前處于有鎖狀態(tài),再繼續(xù)分析第16行到25行,有個關(guān)鍵的Note:(Note: if c != 0 and w == 0 then shared count != 0):簡單說就是:如果一個有鎖狀態(tài)但是沒有寫鎖,那么肯定加了讀鎖。

第18行if條件,就是判斷加了讀鎖,但是當前線程不是鎖擁有的線程,那么獲取鎖失敗,證明讀寫鎖互斥。

第20行到第25行,走到這步,說明 w !=0 ,已經(jīng)獲取了寫鎖,只要不超過寫鎖最大值,那么增加寫狀態(tài)然后就可以成功獲取寫鎖。

如果代碼走到第26行,說明c==0,當前沒有加任何鎖,先執(zhí)行 writerShouldBlock()方法,此方法用來判斷寫鎖是否應(yīng)該阻塞,這塊是對公平與非公平鎖會有不同的邏輯,對于非公平鎖,直接返回false,不需要阻塞,下面是公平鎖執(zhí)行的判斷

public final boolean hasQueuedPredecessors() {
        // The correctness of this depends on head being initialized
        // before tail and on head.next being accurate if the current
        // thread is first in queue.
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

對于公平鎖需要判斷當前等待隊列中是否存在 等于當前線程并且正在排隊等待獲取鎖的線程。

寫鎖的釋放與ReentrantLock的釋放過程基本類似,每次釋放均減少寫狀態(tài),當寫狀態(tài)為0時表示寫鎖已被釋放,從而等待的讀寫線程能夠繼續(xù)訪問讀寫鎖,同時前次寫線程的修改對后續(xù)讀寫線程可見。

讀鎖的獲取與釋放

讀鎖是一個支持重進入的共享鎖,它能夠被多個線程同時獲取。JDK源碼如下:

protected final int tryAcquireShared(int unused) {
            Thread current = Thread.currentThread();
            int c = getState();
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return -1;
            int r = sharedCount(c);
            if (!readerShouldBlock() &&
                r < MAX_COUNT &&
                compareAndSetState(c, c + SHARED_UNIT)) {
                if (r == 0) {
                    firstReader = current;
                    firstReaderHoldCount = 1;
                } else if (firstReader == current) {
                    firstReaderHoldCount++;
                } else {
                    HoldCounter rh = cachedHoldCounter;
                    if (rh == null || rh.tid != getThreadId(current))
                        cachedHoldCounter = rh = readHolds.get();
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    rh.count++;
                }
                return 1;
            }
            return fullTryAcquireShared(current);
        }

第4行到第6行,如果寫鎖被其他線程持有,則直接返回false,獲取讀鎖失敗,證明不同線程間寫讀互斥。

?第8行,readerShouldBlock() 獲取讀鎖是否應(yīng)該阻塞,這兒也同樣要區(qū)分公平鎖和非公平鎖,公平鎖模式需要判斷當前等待隊列中是否存在 等于當前線程并且正在排隊等待獲取鎖的線程,存在則獲取讀鎖需要等待。

非公平鎖模式需要判斷當前等待隊列中第一個是等待寫鎖的,則方法返回true,獲取讀鎖需要等待。

fullTryAcquireShared() 主要是處理讀鎖獲取的完整版本,它處理tryAcquireShared()中沒有處理的CAS錯誤和可重入讀鎖的處理邏輯。

參考文獻

1:《Java并發(fā)編程的藝術(shù)》

2:《Java多線程編程核心技術(shù)》

到此這篇關(guān)于Java多線程讀寫鎖ReentrantReadWriteLock類詳解的文章就介紹到這了。希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • Java的String類中的startsWith方法和endsWith方法示例詳解

    Java的String類中的startsWith方法和endsWith方法示例詳解

    大家應(yīng)該都知道startsWith()方法用于檢測字符串是否以指定的前綴開始,endsWith()方法用于測試字符串是否以指定的后綴結(jié)束,本文就Java的String類中的startsWith方法和endsWith方法給大家詳細講解,感興趣的朋友一起看看吧
    2023-11-11
  • Java設(shè)計模式之java外觀模式詳解

    Java設(shè)計模式之java外觀模式詳解

    這篇文章主要介紹了Java設(shè)計模式之外觀模式(Facade模式)介紹,外觀模式(Facade)的定義:為子系統(tǒng)中的一組接口提供一個一致的界面,需要的朋友可以參考下
    2021-09-09
  • java快速排序和選擇排序?qū)崿F(xiàn)實例解析

    java快速排序和選擇排序?qū)崿F(xiàn)實例解析

    這篇文章主要為大家介紹了java快速排序和選擇排序?qū)崿F(xiàn)實例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-11-11
  • 詳解Springboot集成sentinel實現(xiàn)接口限流入門

    詳解Springboot集成sentinel實現(xiàn)接口限流入門

    這篇文章主要介紹了詳解Springboot集成sentinel實現(xiàn)接口限流入門,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11
  • Mybatis generator如何自動生成代碼

    Mybatis generator如何自動生成代碼

    這篇文章主要介紹了Mybatis generator如何自動生成代碼,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-12-12
  • SpringBoot整合redis中的JSON序列化文件夾操作小結(jié)

    SpringBoot整合redis中的JSON序列化文件夾操作小結(jié)

    在我們?nèi)粘5捻椖块_發(fā)中,使用redis作為緩存,來提高系統(tǒng)訪問速度和緩解系統(tǒng)壓力,在使用中遇到幾個問題,本文給大家詳細總結(jié)下,對SpringBoot整合redis?JSON序列化相關(guān)知識感興趣的朋友一起看看吧
    2022-02-02
  • java后端如何實現(xiàn)防止接口重復(fù)提交

    java后端如何實現(xiàn)防止接口重復(fù)提交

    這篇文章主要介紹了java后端如何實現(xiàn)防止接口重復(fù)提交問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-05-05
  • Java創(chuàng)建和填充PDF表單域方法

    Java創(chuàng)建和填充PDF表單域方法

    在本篇文章中小編給大家分享了關(guān)于Java創(chuàng)建和填充PDF表單域方法和步驟,有需要的朋友們學(xué)習(xí)下。
    2019-01-01
  • SpringBoot?快速實現(xiàn)?api?接口加解密功能

    SpringBoot?快速實現(xiàn)?api?接口加解密功能

    在項目中,為了保證數(shù)據(jù)的安全,我們常常會對傳遞的數(shù)據(jù)進行加密,Spring?Boot接口加密,可以對返回值、參數(shù)值通過注解的方式自動加解密,這篇文章主要介紹了SpringBoot?快速實現(xiàn)?api?接口加解密功能,感興趣的朋友一起看看吧
    2023-10-10
  • MyBatis獲取插入記錄的自增長字段值(ID)

    MyBatis獲取插入記錄的自增長字段值(ID)

    本文分步驟給大家介紹了MyBatis獲取插入記錄的自增長字段值的方法,在文中給大家提到了mybatis返回插入數(shù)據(jù)的自增長id,需要的朋友可以參考下
    2017-11-11

最新評論