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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

聊聊高并发(二十九)解析java.util.concurrent各个组件(十一) 再看看ReentrantReadWriteLock可重入读-写锁

發布時間:2024/1/17 编程问答 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 聊聊高并发(二十九)解析java.util.concurrent各个组件(十一) 再看看ReentrantReadWriteLock可重入读-写锁 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

上一篇聊聊高并發(二十八)解析java.util.concurrent各個組件(十) 理解ReentrantReadWriteLock可重入讀-寫鎖?講了可重入讀寫鎖的基本情況和主要的方法,顯示了如何實現的鎖降級。但是下面幾個問題沒說清楚,這篇補充一下

1. 釋放鎖時的優先級問題,是讓寫鎖先獲得還是先讓讀鎖先獲得

?

2. 是否允許讀線程插隊

3. 是否允許寫線程插隊,因為讀寫鎖一般用在大量讀,少量寫的情況,如果寫線程沒有優先級,那么可能造成寫線程的饑餓

?

關于釋放鎖后是讓寫鎖先獲得還是讓讀鎖先獲得,這里有兩種情況

1. 釋放鎖后,請求獲取寫鎖的線程不在AQS隊列

2. 釋放鎖后,請求獲取寫鎖的線程已經AQS隊列

?

如果是第一種情況,那么非公平鎖的實現下,獲取寫鎖的線程直接嘗試競爭鎖也不用管AQS里面先來的線程。獲取讀鎖的線程只判斷是否已經有線程獲得寫鎖(既Head節點是獨占模式的節點),如果沒有,那么就不用管AQS里面先來的準備獲取讀鎖的線程。

?

?
  • static final class NonfairSync extends Sync {

  • private static final long serialVersionUID = -8159625535654395037L;

  • final boolean writerShouldBlock() {

  • return false; // writers can always barge

  • }

  • final boolean readerShouldBlock() {

  • return apparentlyFirstQueuedIsExclusive();

  • }

  • }


  • 在公平鎖的情況下,獲取讀鎖和寫鎖的線程都判斷是否已經或先來的線程再等待了,如果有,就進入AQS隊列等待。

    ?

    ?
  • static final class FairSync extends Sync {

  • private static final long serialVersionUID = -2274990926593161451L;

  • final boolean writerShouldBlock() {

  • return hasQueuedPredecessors();

  • }

  • final boolean readerShouldBlock() {

  • return hasQueuedPredecessors();

  • }

  • }


  • 對于第二種情況,如果準備獲取寫鎖的線程在AQS隊列里面等待,那么實際是遵循先來先服務的公平性的,因為AQS的隊列是FIFO的隊列。所以獲取鎖的線程的順序是跟它在AQS同步隊列里的位置有關系。

    下面這張圖模擬了AQS隊列中等待的線程節點的情況

    ?

    1. Head節點始終是當前獲得了鎖的線程

    2. 非Head節點在競爭鎖失敗后,acquire方法會不斷地輪詢,于自旋不同的是,AQS輪詢過程中的線程是阻塞等待。

    所以要理解AQS的release釋放動作并不是讓后續節點直接獲取鎖,而是喚醒后續節點unparkSuccessor()。真正獲取鎖的地方還是在acquire方法,被release喚醒的線程繼續輪詢狀態,如果它的前驅是head,并且tryAcquire獲取資源成功了,那么它就獲得鎖

    ?

    ?
  • public final boolean release(int arg) {

  • ??????? if (tryRelease(arg)) {

  • ??????????? Node h = head;

  • ??????????? if (h != null && h.waitStatus != 0)

  • ??????????????? unparkSuccessor(h);

  • ??????????? return true;

  • ??????? }

  • ??????? return false;

  • ??? }

  • ?
  • final boolean acquireQueued(final Node node, int arg) {

  • boolean failed = true;

  • try {

  • boolean interrupted = false;

  • for (;;) {

  • final Node p = node.predecessor();

  • if (p == head && tryAcquire(arg)) {

  • setHead(node);

  • p.next = null; // help GC

  • failed = false;

  • return interrupted;

  • }

  • if (shouldParkAfterFailedAcquire(p, node) &&

  • parkAndCheckInterrupt())

  • interrupted = true;

  • }

  • } finally {

  • if (failed)

  • cancelAcquire(node);

  • }

  • }


  • ?

    3. 圖中Head之后有3個準備獲取讀鎖的線程,最后是1個準備獲取寫鎖的線程。

    那么如果是AQS隊列中的節點獲取鎖

    情況是第一個讀鎖節點先獲得鎖,它獲取鎖的時候就會嘗試釋放共享模式下的一個讀鎖,如果釋放成功了,下一個讀鎖節點就也會被unparkSuccessor喚醒,然后也會獲得鎖。

    如果釋放失敗了,那就把它的狀態標記了PROPAGATE,當它釋放的時候,會再次取嘗試喚醒下一個讀鎖節點

    如果后繼節點是寫鎖,那么就不喚醒

    ?

    ?
  • private void doAcquireShared(int arg) {

  • final Node node = addWaiter(Node.SHARED);

  • boolean failed = true;

  • try {

  • boolean interrupted = false;

  • for (;;) {

  • final Node p = node.predecessor();

  • if (p == head) {

  • int r = tryAcquireShared(arg);

  • if (r >= 0) {

  • setHeadAndPropagate(node, r);

  • p.next = null; // help GC

  • if (interrupted)

  • selfInterrupt();

  • failed = false;

  • return;

  • }

  • }

  • if (shouldParkAfterFailedAcquire(p, node) &&

  • parkAndCheckInterrupt())

  • interrupted = true;

  • }

  • } finally {

  • if (failed)

  • cancelAcquire(node);

  • }

  • }

  • ?
  • ?private void setHeadAndPropagate(Node node, int propagate) {

  • ??????? Node h = head; // Record old head for check below

  • ??????? setHead(node);

  • ???????

  • ??????? if (propagate > 0 || h == null || h.waitStatus < 0) {

  • ??????????? Node s = node.next;

  • ??????????? if (s == null || s.isShared())

  • ??????????????? doReleaseShared();

  • ??????? }

  • ??? }

  • ?
  • private void doReleaseShared() {

  • ??????? for (;;) {

  • ??????????? Node h = head;

  • ??????????? if (h != null && h != tail) {

  • ??????????????? int ws = h.waitStatus;

  • ??????????????? if (ws == Node.SIGNAL) {

  • ??????????????????? if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))

  • ??????????????????????? continue;??????????? // loop to recheck cases

  • ??????????????????? unparkSuccessor(h);

  • ??????????????? }

  • ??????????????? else if (ws == 0 &&

  • ???????????????????????? !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))

  • ??????????????????? continue;??????????????? // loop on failed CAS

  • ??????????? }

  • ??????????? if (h == head)?????????????????? // loop if head changed

  • ??????????????? break;

  • ??????? }

  • ??? }


  • AQS的FIFO隊列保證了在大量讀鎖和少量寫鎖的情況下,寫鎖也不會饑餓。

    ?

    關于讀鎖能不能插隊的問題,非公平性的Sync提供了插隊的可能,但是前提是它在tryAcquire就成功獲得了,如果tryAcquire失敗了,它就得進入AQS隊列排隊,也不會出現讓寫鎖饑餓的情況。

    ?

    關于寫鎖能不能插隊的情況,也是和讀鎖一樣,非公平的Sync提供了插隊的可能,如果tryAcquire獲取失敗,就得進入AQS等待。

    ?

    最后說說為什么Semaphore和ReentrantLock在tryAcquireXX方法就實現了非公平性和公平性,而ReentrantReadWriteLock卻要抽象出readerShouldBlock和writerShouldBlock的方法來單獨處理公平性。

    ?

    ?
  • abstract boolean readerShouldBlock();

  • abstract boolean writerShouldBlock();


  • 原因是Semaphore只支持共享模式,所以它只需要在NonfairSync和FairSync里面實現tryAcquireShared方法就能實現公平性和非公平性。

    ReentrantLock只支持獨占模式,所以它只需要在NonfairSync和FairSync里面實現tryAcquire方法就能實現公平性和非公平性。

    ?

    而ReentrantReadWriteLock即要支持共享和獨占模式,又要支持公平性和非公平性,所以它在基類的Sync里面用tryAcquire和tryAcquireShared方法來區分獨占和共享模式,

    在NonfairSync和FairSync的readerShouldBlock和writerShouldBlock里面實現非公平性和公平性。

    總結

    以上是生活随笔為你收集整理的聊聊高并发(二十九)解析java.util.concurrent各个组件(十一) 再看看ReentrantReadWriteLock可重入读-写锁的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。