一文完全弄懂併發包中的讀寫鎖和Condition實現原理

​導讀

上一篇文章咱們談到了Java併發的基石:
AQS的實現原理 juejin.im/post/5e6f93…
這兩天就有朋友說
ReentrantLock中的Condition條件等待以及ReentrantReadWriteLock讀寫鎖的實現有點繁瑣,問我能不能講下其關鍵實現原理。這篇文章咱們就來談談這個主題。html

這篇文章咱們來Java中的讀寫鎖以及ReentrantLock中Condition條件等待的實現。閱讀完本篇文章,你將瞭解到:node

  1. 讀寫鎖的使用場景和優缺點算法

  2. 讀寫鎖的實現原理數據庫

  3. 如何使用讀寫鎖編程

  4. ReentrantLock的Condition底層是如何實現的後端

若是你不知道ReentrantLock的Condition是幹嗎的,能夠閱讀下個人這篇文章:
ReentrantLock介紹與使用:
https://www.itzhai.com/cpj/introduction-and-use-of-reentrantlock.html
緩存

一、Java中的讀寫鎖

有這樣一種場景:bash

  • 若是對一個共享資源的寫操做沒有讀操做那麼頻繁,這個時候能夠容許多個線程同時讀取共享資源;網絡

  • 可是若是有一個線程想去寫這些共享資源,那麼其餘線程此刻就不該該對這些資源進行讀和寫操做了。數據結構

Java中的ReentrantReadWriteLock正是爲這種場景提供的鎖。該類裏面包括了讀鎖和寫鎖。

1.一、可獲取讀鎖的狀況

  • 沒有其餘線程正在持有寫鎖;

  • 嘗試獲取讀鎖的線程同時持有寫鎖。

1.二、可獲取寫鎖的狀況

  • 沒有其餘線程正在持有讀鎖;

  • 沒有其餘線程正在持有寫鎖。

1.三、讀寫鎖特色

  • 容許併發讀:只要沒有線程正在更新數據,那麼多個線程就能夠同時讀取數據;

  • 只能獨佔寫:只要有一個線程正在寫數據,那麼就會致使其餘線程的讀或者寫均被阻塞;但寫的線程能夠獲取讀鎖,並經過釋放寫鎖,讓鎖降級爲讀鎖;(不能由讀鎖升級爲寫鎖)

  • 只要有一個線程正在讀數據,那麼其餘線程的寫入就會阻塞,直到讀鎖被釋放;

  • 公平性:支持非公平鎖和公平鎖,非公平鎖吞吐量較高;

  • 可重入:不管是讀鎖仍是寫鎖都是支持可重入的。

讀寫鎖能夠增長更新不頻繁而讀取頻繁的共享數據結構的吞吐量。

二、實現原理

ReentrantReadWriteLock是可重入讀寫鎖的實現。咱們先來看看涉及到的類:

image-20200311163334398

咱們能夠看到,ReentrantReadWriteLock中也具備非公平鎖NonfairSync公平鎖FairSync的實現。同時ReentrantReadWriteLock組合了兩把鎖:寫鎖WriteLock讀鎖ReadLock

咱們來看看具體的構造函數:

1public ReentrantReadWriteLock(boolean fair) {2  sync = fair ? new FairSync() : new NonfairSync();3  readerLock = new ReadLock(this);4  writerLock = new WriteLock(this);5}複製代碼

能夠發現,經過參數fair控制是建立非公平鎖仍是公平鎖。同時ReentrantReadWriteLock持有了寫鎖和讀鎖。

而本質上,讀鎖和寫鎖都是經過持有ReentrantReadWriteLock.sync來進行加鎖和釋放鎖的,用的是同一個AQS,Sync類提供類對ReentrantReadWriteLock的支持

1protected ReadLock(ReentrantReadWriteLock lock) {2  sync = lock.sync; // 引用的是ReentrantReadWriteLock的sync實例3}複製代碼
1protected WriteLock(ReentrantReadWriteLock lock) {2  sync = lock.sync; // 引用的是ReentrantReadWriteLock的sync實例3}複製代碼

基於對AQS原理的理解,咱們知道sync是讀寫鎖實現的關鍵,而aqs中核心是state字段和雙端等待隊列。下面咱們來看看具體的實現。

2.一、看代碼以前您必須瞭解的內容

在查看ReentrantReadWriteLock以前,您須要瞭解如下內容:

2.1.一、Sync.HoldCounter類

讀鎖計數器類,爲每一個獲取讀鎖的線程進行計數。Sync類中有一個cachedHoldCounter字段,該字段主要是緩存上一個線程的讀鎖計數器,節省ThreadLocal查找次數。

1static final class HoldCounter {2  // 某個讀線程的重入次數3  int count = 0;4  // 某個線程的tid字段5  // Use id, not reference, to avoid garbage retention6  final long tid = getThreadId(Thread.currentThread());7}複製代碼

2.1.二、Sync.ThreadLocalHoldCounter類

當前線程持有的可重入讀鎖的數量,當數量降低到0的時候進行刪除。

1static final class ThreadLocalHoldCounter2  extends ThreadLocal<HoldCounter> {3  public HoldCounter initialValue() {4    return new HoldCounter();5  }6}複製代碼

2.1.三、讀寫鎖中AQS的state狀態設計

AQS中的state爲了可以同時記錄讀鎖和寫鎖的狀態,把32位變量分爲了兩部分:

image-20200311222446270

如上圖,高16位存儲讀狀態,讀鎖是共享鎖,這裏記錄持有讀鎖的線程數;低16位是寫狀態,寫鎖是排他鎖,這裏0表示沒有線程持有,大於0表示持有線程對鎖的重入次數。

2.1.四、關於讀寫鎖的數據結構

雖然讀寫鎖看起來有兩把鎖,可是底層用的都是同一個state,同一個等待隊列。只不過是經過ReadLock和WriteLock分別提供了讀鎖和寫鎖的API,底層仍是用同一個AQS。以下圖:

image-20200312233429041

因爲讀寫鎖是互斥的,因此線程1獲取寫鎖,線程2獲取讀鎖,併發執行的時候,必定有一個會失敗;

若是是已經獲取了讀鎖的線程嘗試獲取寫鎖,則會獲取成功;

公平模式下,先進入等待隊列的線程先被處理;非公平模式下,若是嘗試獲取寫鎖的線程節點在頭節點後面,嘗試獲取讀鎖的線程要讓步,進入等待隊列;

線程節點獲取到讀鎖以後,會判斷下一個節點是否處於共享模式,若是是則會一直傳播並喚醒後續共享模式節點;

若是有其餘線程獲取了寫鎖,那麼獲取寫鎖就會被阻塞。

公平和非公平是針對等待隊列中的線程節點的處理來講的:

  • 公平模式通常都是從隊列頭開始處理,而且若是等待隊列還有待處理節點,新的線程所有都入等待隊列

  • 非公平模式通常無論等待隊列裏面有沒有待處理節點,都會先嚐試競爭獲取鎖;特殊狀況:若是等待隊列中有寫鎖線程,那麼新來的讀鎖線程必須排隊讓寫鎖線程先進行處理。

其實關於讀寫鎖的原理就差很少是這麼多了。

如下是詳細的代碼分析,可能會比較枯燥,爲了不讓你們一頭陷入源碼中,因而在上面先把源碼作的事情都給講出來了。建議感興趣的同窗打開電腦跟蹤源碼一塊兒來閱讀。

2.二、ReadLock實現原理

2.2.一、lock

查看ReadLock的lock相關方法,調用的是AQS的acquireShared方法,該方法會以共享模式獲取鎖:

1public final void acquireShared(int arg) {2  // 嘗試獲取鎖3  if (tryAcquireShared(arg) < 0)4    // 若是獲取鎖失敗了,那麼會進入ASQ的等待隊列,等待被喚醒後從新嘗試獲取鎖5    doAcquireShared(arg);6}複製代碼

下面看看關鍵獲取鎖的tryAcquireShared方法,該方法主要處理邏輯:

  • 由於讀寫是互斥的,若是另外一個線程持有寫鎖,則失敗;

  • 不然,此線程具有鎖定write狀態的條件,所以判斷是否應該進入阻塞。若是不是,請嘗試CAS獲取讀鎖許可並更新讀鎖計數。請注意,該步驟不檢查重入,這將推遲到最後fullTryAcquireShared方法;

  • 若是第2步失敗,或者因爲線程不符合鎖定條件或者CAS失敗或讀鎖計數飽和,將會使用fullTryAcquireShared進一步重試。

下面是詳細的說明:

1protected final int tryAcquireShared(int unused) { 2  Thread current = Thread.currentThread(); 3  int c = getState(); 4  // 若是存在寫鎖,而且寫鎖不是當前線程,則直接失敗讓線程進入等待隊列 5  if (exclusiveCount(c) != 0 && 6      getExclusiveOwnerThread() != current) 7    return -1; 8  int r = sharedCount(c); 9  // 判斷讀鎖是否應該被阻塞,公平模式下,先進入等待隊列則先被處理;非公平模式下寫鎖優先級比較高,若是頭節點的下一個節點不是共享模式,便是嘗試獲取寫鎖的線程,讀鎖須要讓步10  if (!readerShouldBlock() &&11      // 讀鎖是否已到達獲取上線12      r < MAX_COUNT &&13      // CAS修改讀鎖狀態,+114      compareAndSetState(c, c + SHARED_UNIT)) {15      // 獲取讀鎖成功16      if (r == 0) {17        // 若是是第一個獲取讀鎖的線程,也就是把讀鎖狀態從0變到1的那個線程,那麼存入firstReader中18        firstReader = current;19        // firstReader持有鎖=120        firstReaderHoldCount = 1;21      } else if (firstReader == current) {22        // firstReader已是當前線程,則firstReaderHoldCount++23        firstReaderHoldCount++;24      } else { // 讀鎖數量不爲0,而且第一個讀線程不爲當前線程25        // 獲取緩存讀鎖計數器26        HoldCounter rh = cachedHoldCounter;27        if (rh == null || rh.tid != getThreadId(current))28          // 緩存讀鎖計數器爲空或者計數器不是當前線程的,則嘗試經過ThreadLocal獲取當前線程對應的計數器29          cachedHoldCounter = rh = readHolds.get();30        else if (rh.count == 0)31          readHolds.set(rh);32        rh.count++;33      }34      return 1;35    }36    // 以上執行失敗,則進入該邏輯37    return fullTryAcquireShared(current);38}複製代碼

讓咱們接着看fullTryAcquireShared方法,這個方法可知,只有其餘線程持有寫鎖,或者使用的是公平鎖而且頭節點後面還有其餘等待的線程,或者頭節點後面的節點不是共享模式,或者讀鎖計數器達到了上限,則阻塞,不然一直會循環嘗試獲取鎖:

1final int fullTryAcquireShared(Thread current) { 2  HoldCounter rh = null; 3  for (;;) { 4    int c = getState(); 5    // 若是存在寫鎖,而且寫鎖不是當前線程,則返回false 6    if (exclusiveCount(c) != 0) { 7      if (getExclusiveOwnerThread() != current) 8        return -1; 9      // else we hold the exclusive lock; blocking here10      // would cause deadlock.11    // 不存在寫鎖,繼續判斷是否應該阻塞:若是是公平鎖而且頭節點後有其餘等待的線程,則阻塞,若是是非公平鎖,判斷頭節點後面的節點是否共享模式,若是不是則阻塞12    } else if (readerShouldBlock()) {13      // Make sure we're not acquiring read lock reentrantly14      // 若是當前線程是firstReader,說明是重入15      if (firstReader == current) {16        // assert firstReaderHoldCount > 0;17      } else {18        // 進入該分支,說明沒有讀寫鎖衝突,而且不是重入,當前線程也不是firstReader19        if (rh == null) {20          rh = cachedHoldCounter;21          // 判斷上一個獲取到鎖的線程是否當前線程,不是則進入AQS等待隊列22          if (rh == null || rh.tid != getThreadId(current)) {23            rh = readHolds.get();24            if (rh.count == 0)25              readHolds.remove();26          }27        }28        // rh.count == 0 表示rh是剛新獲取到的,直接返回,進入等待隊列29        if (rh.count == 0)30          return -1;31      }32    }33    // 共享鎖達到上限了34    if (sharedCount(c) == MAX_COUNT)35      throw new Error("Maximum lock count exceeded");36    // 讀鎖自增,如下代碼與上一個方法中的相似37    if (compareAndSetState(c, c + SHARED_UNIT)) {38      if (sharedCount(c) == 0) {39        firstReader = current;40        firstReaderHoldCount = 1;41      } else if (firstReader == current) {42        firstReaderHoldCount++;43      } else {44        if (rh == null)45          rh = cachedHoldCounter;46        if (rh == null || rh.tid != getThreadId(current))47          rh = readHolds.get();48        else if (rh.count == 0)49          readHolds.set(rh);50        rh.count++;51        cachedHoldCounter = rh; // cache for release52      }53      return 1;54    }55  }56}複製代碼

最後咱們來看看doAcquireShared方法:

1private void doAcquireShared(int arg) { 2  // 添加一個共享等待節點 3  final Node node = addWaiter(Node.SHARED); 4  boolean failed = true; 5  try { 6    boolean interrupted = false; 7    for (;;) { 8      // 判斷新增的節點的前一個節點是否頭節點 9      final Node p = node.predecessor();10      if (p == head) { // 是頭節點,那麼在此嘗試獲取共享鎖11        int r = tryAcquireShared(arg);12        if (r >= 0) { 13          // 獲取成功,把當前節點變爲新的head節點,而且檢查後續節點是否能夠在共享模式下等待,而且容許繼續傳播,則調用doReleaseShared繼續喚醒下一個節點嘗試獲取鎖14          setHeadAndPropagate(node, r);15          p.next = null; // help GC16          if (interrupted)17            selfInterrupt();18          failed = false;19          return;20        }21      }22      // 阻塞節點23      if (shouldParkAfterFailedAcquire(p, node) &&24          parkAndCheckInterrupt())25        interrupted = true;26    }27  } finally {28    if (failed)29      // 取消獲取鎖30      cancelAcquire(node);31  }32}複製代碼

2.2.二、unlock

接下來咱們看看釋放鎖的代碼。

1public void unlock() {2  sync.releaseShared(1);3}複製代碼

AbstractQueuedSynchronizer.releaseShared()

1public final boolean releaseShared(int arg) {2  if (tryReleaseShared(arg)) {3    doReleaseShared();4    return true;5  }6  return false;7}複製代碼

主要處理方法是tryReleaseShared,該方法主要是清理ThreadLocal中的鎖計數器,而後CAS修改讀鎖個數減1:

1protected final boolean tryReleaseShared(int unused) { 2  Thread current = Thread.currentThread(); 3  if (firstReader == current) { 4    // assert firstReaderHoldCount > 0; 5    if (firstReaderHoldCount == 1) 6      firstReader = null; 7    else 8      firstReaderHoldCount--; 9  } else {10    HoldCounter rh = cachedHoldCounter;11    if (rh == null || rh.tid != getThreadId(current))12      rh = readHolds.get();13    int count = rh.count;14    if (count <= 1) {15      readHolds.remove();16      if (count <= 0)17        throw unmatchedUnlockException();18    }19    --rh.count;20  }21  for (;;) {22    int c = getState();23    int nextc = c - SHARED_UNIT;24    if (compareAndSetState(c, nextc))25      // Releasing the read lock has no effect on readers,26      // but it may allow waiting writers to proceed if27      // both read and write locks are now free.28      return nextc == 0;29  }30}複製代碼

2.三、WriteLock實現原理

2.3.一、lock

查看WriteLock的lock鎖相關方法,調用的是sync.acquire方法,該方法直接繼承了ASQ的acquire()方法的實現:

1public void lock() {2    sync.acquire(1);3}複製代碼

與ReentrantLock的實現區別在具體的tryAcquire()方法的實現,咱們來看看ReentrantReadWriteLock.Sync中該方法的實現,主要作了如下事情:

  • 若是讀鎖數量>0,或者寫鎖數量>0,而且不是重入的,那麼直接失敗了;

  • 若是鎖數量爲0,那麼該線程有資格獲取到寫鎖,進而嘗試獲取。

1protected final boolean tryAcquire(int acquires) { 2  Thread current = Thread.currentThread(); 3  int c = getState(); 4  int w = exclusiveCount(c); 5  if (c != 0) { // 存在讀鎖或者寫鎖 6    // 不存在寫鎖,或者當前線程不是寫鎖持有的線程,那麼直接失敗 7    if (w == 0 || current != getExclusiveOwnerThread()) 8      return false; 9    // 寫鎖超多最大數量限制,也直接失敗10    if (w + exclusiveCount(acquires) > MAX_COUNT)11      throw new Error("Maximum lock count exceeded");12    // Reentrant acquire13    // 寫鎖持有的線程重入,直接修改state便可14    setState(c + acquires);15    return true;16  }17  // 判斷是否應該阻塞:非公平模式,無需阻塞,公平模式若是前面有其餘節點則須要排隊阻塞18  if (writerShouldBlock() ||19      // 嘗試獲取寫鎖20      !compareAndSetState(c, c + acquires))21    return false;22  setExclusiveOwnerThread(current);23  return true;24}複製代碼

2.3.二、unlock

查看WriteLock的unlock相關方法,調用的是sync.release方法,該方法直接繼承了AQS的release實現:

1public void unlock() {2  sync.release(1);3}複製代碼

如下是release方法:

1public final boolean release(int arg) { 2  // 嘗試釋放鎖 3  if (tryRelease(arg)) { 4    // 釋放鎖成功,則喚醒隊列中頭節點後的一個線程 5    Node h = head; 6    if (h != null && h.waitStatus != 0) 7      unparkSuccessor(h); 8    return true; 9  }10  return false;11}複製代碼

釋放鎖的邏輯主要在tryRelease方法,下面是詳細代碼:

1protected final boolean tryRelease(int releases) { 2  // 若是當前線程沒有獲取寫鎖,則釋放直接拋異常 3  if (!isHeldExclusively()) 4    throw new IllegalMonitorStateException(); 5  int nextc = getState() - releases; 6  boolean free = exclusiveCount(nextc) == 0; 7  // 若是當前線程徹底釋放了寫鎖,則去除獨佔標識 8  if (free) 9    setExclusiveOwnerThread(null);10  // 修改state11  setState(nextc);12  return free;13}複製代碼

三、讀寫鎖使用例子

下面是讀寫鎖的使用例子,該例子實現了一個支持併發訪問的ArrayList。

由於讀寫鎖是互斥的,保證了不會由於寫致使讀取出現的不一致。

代碼以下:

1public class ReentrantReadWriteLockTest {  2  3    static final int READER_SIZE = 10;  4    static final int WRITER_SIZE = 2;  5  6    public static void main(String[] args) {  7        Integer[] initialElements = {33, 28, 86, 99};  8  9        ReadWriteList<Integer> sharedList = new ReadWriteList<>(initialElements); 10 11        for (int i = 0; i < WRITER_SIZE; i++) { 12            new Writer(sharedList).start(); 13        } 14 15        for (int i = 0; i < READER_SIZE; i++) { 16            new Reader(sharedList).start(); 17        } 18 19    } 20 21} 22 23class Reader extends Thread { 24    private ReadWriteList<Integer> sharedList; 25 26    public Reader(ReadWriteList<Integer> sharedList) { 27        this.sharedList = sharedList; 28    } 29 30    public void run() { 31        Random random = new Random(); 32        int index = random.nextInt(sharedList.size()); 33        Integer number = sharedList.get(index); 34 35        System.out.println(getName() + " -> get: " + number); 36 37        try { 38            Thread.sleep(100); 39        } catch (InterruptedException ie ) { ie.printStackTrace(); } 40 41    } 42} 43 44class Writer extends Thread { 45    private ReadWriteList<Integer> sharedList; 46 47    public Writer(ReadWriteList<Integer> sharedList) { 48        this.sharedList = sharedList; 49    } 50 51    public void run() { 52        Random random = new Random(); 53        int number = random.nextInt(100); 54        sharedList.add(number); 55 56        try { 57            Thread.sleep(100); 58            System.out.println(getName() + " -> put: " + number); 59        } catch (InterruptedException ie ) { ie.printStackTrace(); } 60    } 61} 62 63/** 64 * 支持併發讀寫的ArrayList 65 */ 66class ReadWriteList<E> { 67    private List<E> list = new ArrayList<>(); 68    private ReadWriteLock rwLock = new ReentrantReadWriteLock(); 69 70    public ReadWriteList(E... initialElements) { 71        list.addAll(Arrays.asList(initialElements)); 72    } 73 74    public void add(E element) { 75        Lock writeLock = rwLock.writeLock(); 76        writeLock.lock(); 77 78        try { 79            list.add(element); 80        } finally { 81            writeLock.unlock(); 82        } 83    } 84 85    public E get(int index) { 86        Lock readLock = rwLock.readLock(); 87        readLock.lock(); 88 89        try { 90            return list.get(index); 91        } finally { 92            readLock.unlock(); 93        } 94    } 95 96    public int size() { 97        Lock readLock = rwLock.readLock(); 98        readLock.lock(); 99100        try {101            return list.size();102        } finally {103            readLock.unlock();104        }105    }106107}複製代碼

四、ReentrantLock的Condition實現原理

接下來咱們來ReentrantLock中的Condition實現原理。

有以下的ReentrantLock和Condition:

1// 鎖和條件變量2private final Lock lock = new ReentrantLock();3// 條件4private final Condition condition1 = lock.newCondition();複製代碼

下面來看看執行await和signal的流程。

4.一、await等待

通常地,只有線程獲取到lock以後,纔可使用condition的await方法。假設此時線程1獲取到了ReentrantLock鎖,在執行代碼邏輯的時候,發現某些條件不符合,因而調用瞭如下代碼:

1while(xxx條件不知足) {2  condition1.await();3}複製代碼

此時AQS主要執行如下動做:

  1. 線程1把本身包裝成節點,waitStatus設爲CONDITION(-2),追加到ConditionObject中的條件隊列(每一個ConditionObject有一個本身的條件隊列);

  2. 線程1釋放鎖,把state設置爲0;

  3. 而後喚醒等待隊列中head節點的下一個節點;

以下:

image-20200314121818770

接下來進入一個循環,若是判斷到當前線程的節點不在等待隊列,那麼會一直讓當前線程阻塞,代碼以下:

1while (!isOnSyncQueue(node)) {2  LockSupport.park(this);3  if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)4    break;5}複製代碼

這個時候已經喚醒其餘線程繼續處理了,只有其餘線程執行了condition1.signal或者condition1.signalAll以後,纔會喚醒線程1進行處理後續的流程。

4.二、signal喚醒

當另外一個線程執行了 condition1.signal以後,主要是作了如下事情:

  1. 把條件隊列中的第一個節點追加到等待隊列中;

  2. 把等待隊列原來尾節點的waitStatus設置爲SIGNAL。

image-20200314144725065

而後繼續處理本身的事情,本身的事情處理完成以後,會釋放鎖,喚醒等待隊列中head節點的下一個節點線程進行工做。

4.三、await恢復後繼續執行

被喚醒的若是是以前執行了await方法的線程,那麼該線程會接着就像往await方法裏面阻塞處的下面繼續執行,下面是源碼:

1// 若是當前節點不在等待隊列,會一直進行阻塞 2while (!isOnSyncQueue(node)) { 3  LockSupport.park(this); 4  if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) 5    break; 6} 7// 該方法主要作如下事情: 8// 1.嘗試獲取ReentrantLock鎖 9// 2.獲取成功,把如今線程節點變爲新的head節點10// 3. 不然根據繼續休眠等待11if (acquireQueued(node, savedState) && interruptMode != THROW_IE)12  interruptMode = REINTERRUPT;13if (node.nextWaiter != null) // 若是等待節點被取消了,那麼從條件隊列中移除14  unlinkCancelledWaiters();15if (interruptMode != 0)16  reportInterruptAfterWait(interruptMode);複製代碼

能夠發現,這裏主要是判斷到當前線程節點已經放入等待隊列了,那麼會嘗試獲取鎖,獲取成功則繼續往下執行代碼。

第一節咱們知道只有線程獲取到ReentrantLock的鎖以後才能夠繼續往下執行,中間可能會由於執行await而進入條件隊列並釋放鎖,最後又會被喚醒從新獲取鎖,繼續往下執行。最後按照書寫規範,咱們必定會在代碼中執行ReentrantLock.unlock()釋放鎖,而後繼續喚醒等待隊列後續線程繼續執行。

這篇文章的內容就差很少介紹到這裏了,可以閱讀到這裏的朋友真的是頗有耐心,爲你點個贊。

本文爲arthinking基於相關技術資料和官方文檔撰寫而成,確保內容的準確性,若是你發現了有何錯漏之處,煩請高擡貴手幫忙指正,萬分感激。

你們能夠關注個人博客:itzhai.com 獲取更多文章,我將持續更新後端相關技術,涉及JVM、Java基礎、架構設計、網絡編程、數據結構、數據庫、算法、併發編程、分佈式系統等相關內容。

若是您以爲讀完本文有所收穫的話,能夠關注個人帳號,或者點個贊吧,碼字不易,您的支持就是我寫做的最大動力,再次感謝!

關注個人公衆號,及時獲取最新的文章。



更多文章

JVM系列專題:公衆號發送 JVM



·END·

 訪問IT宅(itzhai.com)查看個人博客更多文章

掃碼關注及時獲取新內容↓↓↓



Java架構雜談

Java後端技術架構 · 技術專題 · 經驗分享

blog: itzhai.com


碼字不易,若有收穫,點個「贊」哦~


本文做者:arthinking
博客連接:
ReentrantLock的Conditiion原理解析 https://www.itzhai.com/cpj/analysis-of-reentrantlocks-condition-principle.html
ReentrantReadWriteLock介紹與使用 https://www.itzhai.com/cpj/introduction-and-use-of-reentrantreadwritelock.html版權聲明:BY-NC-SA許可協議:創做不易,如需轉載,請務必附加上博客連接,謝謝!

相關文章
相關標籤/搜索