日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 综合教程 >内容正文

综合教程

ReentrantReadWriteLock读写锁详解

發(fā)布時間:2023/12/13 综合教程 36 生活家
生活随笔 收集整理的這篇文章主要介紹了 ReentrantReadWriteLock读写锁详解 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

一、讀寫鎖簡介 

現(xiàn)實中有這樣一種場景:對共享資源有讀和寫的操作,且寫操作沒有讀操作那么頻繁。在沒有寫操作的時候,多個線程同時讀一個資源沒有任何問題,所以應(yīng)該允許多個線程同時讀取共享資源;但是如果一個線程想去寫這些共享資源,就不應(yīng)該允許其他線程對該資源進(jìn)行讀和寫的操作了。

  針對這種場景,JAVA的并發(fā)包提供了讀寫鎖ReentrantReadWriteLock,它表示兩個鎖,一個是讀操作相關(guān)的鎖,稱為共享鎖;一個是寫相關(guān)的鎖,稱為排他鎖,描述如下:

線程進(jìn)入讀鎖的前提條件:
沒有其他線程的寫鎖,
沒有寫請求或者有寫請求,但調(diào)用線程和持有鎖的線程是同一個

線程進(jìn)入寫鎖的前提條件:
沒有其他線程的讀鎖
沒有其他線程的寫鎖

而讀寫鎖有以下三個重要的特性:

二、源碼解讀

1、內(nèi)部類

讀寫鎖實現(xiàn)類中有許多內(nèi)部類,我們先來看下這些類的定義:

public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable

讀寫鎖并沒有實現(xiàn)Lock接口,而是實現(xiàn)了ReadWriteLock。并發(fā)系列中真正實現(xiàn)Lock接口的并不多,除了前面提到過的重入鎖(ReentrantLock),另外就是讀寫鎖中為了實現(xiàn)讀鎖和寫鎖的兩個內(nèi)部類:

public static class ReadLock implements Lock, java.io.Serializable
public static class WriteLock implements Lock, java.io.Serializable

另外讀寫鎖也設(shè)計成模板方法模式,通過繼承隊列同步器,提供了公平與非公平鎖的特性:

static abstract class Sync extends AbstractQueuedSynchronizer
final static class NonfairSync extends Sync
final static class FairSync extends Sync

2、讀寫狀態(tài)的設(shè)計

同步狀態(tài)在前面重入鎖的實現(xiàn)中是表示被同一個線程重復(fù)獲取的次數(shù),即一個整形變量來維護(hù),但是之前的那個表示僅僅表示是否鎖定,而不用區(qū)分是讀鎖還是寫鎖。而讀寫鎖需要在同步狀態(tài)(一個整形變量)上維護(hù)多個讀線程和一個寫線程的狀態(tài)。

讀寫鎖對于同步狀態(tài)的實現(xiàn)是在一個整形變量上通過“按位切割使用”:將變量切割成兩部分,高16位表示讀,低16位表示寫。

假設(shè)當(dāng)前同步狀態(tài)值為S,get和set的操作如下:

1、獲取寫狀態(tài):

S&0x0000FFFF:將高16位全部抹去

2、獲取讀狀態(tài):

S>>>16:無符號補0,右移16位

3、寫狀態(tài)加1:

S+1

4、讀狀態(tài)加1:

  S+(1<<16)即S + 0x00010000

在代碼層嗎的判斷中,如果S不等于0,當(dāng)寫狀態(tài)(S&0x0000FFFF),而讀狀態(tài)(S>>>16)大于0,則表示該讀寫鎖的讀鎖已被獲取。

3、寫鎖的獲取與釋放

protected final boolean tryAcquire(int acquires) {
            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");
            }
            if ((w == 0 && writerShouldBlock(current)) ||
                !compareAndSetState(c, c + acquires))
                return false;
            setExclusiveOwnerThread(current);
            return true;
        }

1、c是獲取當(dāng)前鎖狀態(tài);w是獲取寫鎖的狀態(tài)。

2、如果鎖狀態(tài)不為零,而寫鎖的狀態(tài)為0,則表示讀鎖狀態(tài)不為0,所以當(dāng)前線程不能獲取寫鎖。或者鎖狀態(tài)不為零,而寫鎖的狀態(tài)也不為0,但是獲取寫鎖的線程不是當(dāng)前線程,則當(dāng)前線程不能獲取寫鎖。

寫鎖是一個可重入的排它鎖,在獲取同步狀態(tài)時,增加了一個讀鎖是否存在的判斷。

寫鎖的釋放與ReentrantLock的釋放過程類似,每次釋放將寫狀態(tài)減1,直到寫狀態(tài)為0時,才表示該寫鎖被釋放了。

4、讀鎖的獲取與釋放

 1 protected final int tryAcquireShared(int unused) {
 2             Thread current = Thread.currentThread();
 3             int c = getState();
 4             if (exclusiveCount(c) != 0 &&
 5                 getExclusiveOwnerThread() != current)
 6                 return -1;
 7             if (sharedCount(c) == MAX_COUNT)
 8                 throw new Error("Maximum lock count exceeded");
 9             if (!readerShouldBlock(current) &&
10                 compareAndSetState(c, c + SHARED_UNIT)) {
11                 HoldCounter rh = cachedHoldCounter;
12                 if (rh == null || rh.tid != current.getId())
13                     cachedHoldCounter = rh = readHolds.get();
14                 rh.count++;
15                 return 1;
16             }
17             return fullTryAcquireShared(current);
18         }

1、讀鎖是一個支持重進(jìn)入的共享鎖,可以被多個線程同時獲取。

2、在沒有寫狀態(tài)為0時,讀鎖總會被成功獲取,而所做的也只是增加讀狀態(tài)(線程安全)

3、讀狀態(tài)是所有線程獲取讀鎖次數(shù)的總和,而每個線程各自獲取讀鎖的次數(shù)只能選擇保存在ThreadLocal中,由線程自身維護(hù)。

讀鎖的每次釋放均減小狀態(tài)(線程安全的,可能有多個讀線程同時釋放鎖),減小的值是1<<16。

5、鎖降級

鎖降級指的是寫鎖降級為讀鎖:把持住當(dāng)前擁有的寫鎖,再獲取到讀鎖,隨后釋放先前擁有的寫鎖的過程。

而鎖升級是將讀鎖變成寫鎖,但是ReentrantReadWriteLock不支持這種方式。

我們先來看鎖升級的程序:

1  ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
2         rwl.readLock().lock();
3         System.out.println("get readLock");
4         rwl.writeLock().lock();
5         System.out.println("get writeLock");

這種線獲取讀鎖,不釋放緊接著獲取寫鎖,會導(dǎo)致死鎖!!!

1 ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
2         rwl.writeLock().lock();
3         System.out.println("get writeLock");
4         rwl.readLock().lock();
5         System.out.println("get readLock");

這個過程跟上面的剛好相反,程序可以正常運行不會出現(xiàn)死鎖。但是鎖降級并不會自動釋放寫鎖。仍然需要顯示的釋放。

由于讀寫鎖用于讀多寫少的場景,天然的使用于實現(xiàn)緩存,下面看一個簡易的實現(xiàn)緩存的DEMO:

 1 import java.util.HashMap;
 2 import java.util.concurrent.locks.ReadWriteLock;
 3 import java.util.concurrent.locks.ReentrantReadWriteLock;
 4 
 5 
 6 public class CachedTest
 7 {
 8     volatile HashMap<String,String> cacheMap = new HashMap<String,String>();
 9     
10     ReadWriteLock rwLock = new ReentrantReadWriteLock();
11     
12     public String getS(String key)
13     {
14         rwLock.readLock().lock();
15         String value = null;
16         try
17         {
18             if(cacheMap.get(key) == null)
19             {
20                 rwLock.readLock().unlock();
21                 rwLock.writeLock().lock();
22                 try
23                 {
24                     //這里需要再次判斷,防止后面阻塞的線程再次放入數(shù)據(jù)
25                     if(cacheMap.get(key) == null)
26                     {
27                         value = "" + Thread.currentThread().getName();
28                         cacheMap.put(key, value);
29                         System.out.println(Thread.currentThread().getName() + "put the value" + value);
30                     }
31                 }
32                 finally
33                 {
34                     //這里是鎖降級,讀鎖的獲取與寫鎖的釋放順序不能顛倒
35                     rwLock.readLock().lock();
36                     rwLock.writeLock().unlock();
37                 }
38             }
39         }
40         finally
41         {
42             rwLock.readLock().unlock();
43         }
44         return cacheMap.get(key);
45     }
46 }

1、業(yè)務(wù)邏輯很好理解,一個線程進(jìn)來先獲取讀鎖,如果map里面沒有值,則釋放讀鎖,獲取寫鎖,將該線程的value放入map中。

2、這里有兩次value為空的判斷,第一次判斷很好理解,第二次判斷是防止當(dāng)前線程在獲取寫鎖的時候,其他的線程阻塞在獲取寫鎖的地方。當(dāng)當(dāng)前線程將vaule放入map之后,釋放寫鎖。如果這個位置沒有value的判斷,后續(xù)獲得寫鎖的線程以為map仍然為空,會再一次將value值放入map中,覆蓋前面的value值,顯然這不是我們愿意看見的。

3、在第35行的位置,這里處理的鎖降級的邏輯。按照我們正常的邏輯思維,因為是先釋放寫鎖,再獲取讀鎖。那么鎖降級為什么要這么處理呢?答案是為了保證數(shù)據(jù)的可見性,因為如果當(dāng)前線程不獲取讀鎖而是直接釋放寫鎖,如果該線程在釋放寫鎖與獲取讀鎖這個時間段內(nèi),有另外一個線程獲取的寫鎖并修改了數(shù)據(jù),那么當(dāng)前線程無法感知數(shù)據(jù)的變更。如果按照鎖降級的原則來處理,那么當(dāng)前線程獲取到讀鎖之后,會阻塞其他線程獲取寫鎖,那么數(shù)據(jù)就不會被其他線程所改動,這樣就保證了數(shù)據(jù)的一致性。

總結(jié)

以上是生活随笔為你收集整理的ReentrantReadWriteLock读写锁详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。