摘要:所以就有了讀寫鎖。只要沒有,讀取鎖可以由多個線程同時保持。其讀寫鎖為兩個內(nèi)部類都實現(xiàn)了接口。讀寫鎖同樣依賴自定義同步器來實現(xiàn)同步狀態(tài)的,而讀寫狀態(tài)就是其自定義同步器的狀態(tài)。判斷申請寫鎖數(shù)量是否超標(biāo)超標(biāo)則直接異常,反之則設(shè)置共享狀態(tài)。
一、寫在前面
在上篇我們聊到了可重入鎖(排它鎖)ReentrantLcok ,具體參見《J.U.C|可重入鎖ReentrantLock》
ReentrantLcok 屬于排它鎖,本章我們再來一起聊聊另一個我們工作中出鏡率很高的讀-寫鎖。
二、簡介重入鎖ReentrantLock是排他鎖(互斥鎖),排他鎖在同一時刻僅有一個線程可訪問,但是在大多數(shù)場景下,大部分時間都是提供讀服務(wù)的,而寫服務(wù)占用極少的時間,然而讀服務(wù)不存在數(shù)據(jù)競爭的問題,如果一個線程在讀時禁止其他線程讀勢必會降低性能。所以就有了讀寫鎖。
讀寫鎖內(nèi)部維護(hù)著一對鎖,一個讀鎖和一個寫鎖。通過分離讀鎖和寫鎖,使得并發(fā)性比一般排他鎖有著顯著的提升。
讀寫鎖在同一時間可以允許多個讀線程同時訪問,但是寫線程訪問時,所有的讀線程和寫線程都會阻塞。
主要有以下特征:
公平性選擇:支持非公平(默認(rèn))和公平的鎖獲取方式,吞吐量還是非公平優(yōu)于公平。
重進(jìn)入:該鎖支持重進(jìn)入,以讀寫線程為列,讀線程在獲取到讀鎖之后,能再次獲取讀鎖。而寫線程在獲取寫鎖后能夠再次獲取寫鎖,同時也可以獲取讀鎖。
鎖降級:遵循獲取寫鎖、讀鎖再釋放寫鎖的次序,寫鎖能夠降級成為讀鎖。
讀寫鎖最多支持65535個遞歸寫入鎖和65535個遞歸讀取鎖。 鎖降級:遵循獲取寫鎖、獲取讀鎖在釋放寫鎖的次序,寫鎖能夠降級成為讀鎖 讀寫鎖ReentrantReadWriteLock實現(xiàn)接口ReadWriteLock,該接口維護(hù)了一對相關(guān)的鎖,一個用于只讀操作,另一個用于寫入操作。只要沒有 writer,讀取鎖可以由多個 reader 線程同時保持。三、主要方法介紹
讀寫鎖ReentrantReadWriteLock 實現(xiàn)了ReadWriteLock 接口,該接口維護(hù)一對相關(guān)的鎖即讀鎖和寫鎖。
public interface ReadWriteLock { /** * Returns the lock used for reading. * * @return the lock used for reading */ Lock readLock(); /** * Returns the lock used for writing. * * @return the lock used for writing */ Lock writeLock(); }
ReadWriteLock定義了兩個方法。readLock()返回用于讀操作的鎖,writeLock()返回用于寫操作的鎖。ReentrantReadWriteLock定義如下:
/** 內(nèi)部類 讀鎖*/ private final ReentrantReadWriteLock.ReadLock readerLock; /** 內(nèi)部類 寫鎖*/ private final ReentrantReadWriteLock.WriteLock writerLock; /** 執(zhí)行所有同步機(jī)制 */ final Sync sync; // 默認(rèn)實現(xiàn)非公平鎖 public ReentrantReadWriteLock() { this(false); } // 利用給定的公平策略初始化ReentrantReadWriteLock public ReentrantReadWriteLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); readerLock = new ReadLock(this); writerLock = new WriteLock(this); } // 返回寫鎖 public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; } //返回讀鎖 public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; } // 實現(xiàn)同步器,也是實現(xiàn)鎖的核心 abstract static class Sync extends AbstractQueuedSynchronizer { // 省略實現(xiàn)代碼 } // 公平鎖的實現(xiàn) static final class FairSync extends Sync { // 省略實現(xiàn)代碼 } // 非公平鎖的實現(xiàn) static final class NonfairSync extends Sync { // 省略實現(xiàn)代碼 } // 讀鎖實現(xiàn) public static class ReadLock implements Lock, java.io.Serializable { // 省略實現(xiàn)代碼 } // 寫鎖實現(xiàn) public static class WriteLock implements Lock, java.io.Serializable { // 省略實現(xiàn)代碼 }
ReentrantReadWriteLock 和 ReentrantLock 其實都一樣,鎖核心都是Sync, 讀鎖和寫鎖都是基于Sync來實現(xiàn)的。從這分析其實ReentrantReadWriteLock就是一個鎖,只不過內(nèi)部根據(jù)不同的場景設(shè)計了兩個不同的實現(xiàn)方式。其讀寫鎖為兩個內(nèi)部類: ReadLock、WriteLock 都實現(xiàn)了Lock 接口。
讀寫鎖同樣依賴自定義同步器來實現(xiàn)同步狀態(tài)的, 而讀寫狀態(tài)就是其自定義同步器的狀態(tài)?;叵隦eentantLock 中自定義同步器的實現(xiàn),同步狀態(tài)表示鎖被一個線程重復(fù)獲取的次數(shù),而讀寫鎖中的自定義同步器需要在一個同步狀態(tài)(一個整型變量)上維護(hù)多個讀線程和寫線程的狀況,而該狀態(tài)的設(shè)計成為關(guān)鍵。
如何在一個整型上維護(hù)多種狀態(tài),那就需要‘按位切割使用’這個變量,讀寫鎖將變量切割成兩部分,高16位表示讀,低16位表示寫。
分割之后,讀寫鎖是如何迅速確定讀鎖和寫鎖的狀態(tài)呢?通過位運(yùn)算,假如當(dāng)前同步狀態(tài)為S,那么寫狀態(tài)等于 S & 0x0000FFFF(將高16位全部抹去),讀狀態(tài)等于S >>> 16(無符號補(bǔ)0右移16位)。代碼如下:
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; }四、寫鎖的獲取與釋放
寫鎖是一個支持重入的排他鎖,如果當(dāng)前線程已經(jīng)獲取了寫鎖,則增加寫狀態(tài)。如果當(dāng)前線程獲取寫鎖時讀鎖已經(jīng)被獲取或者該線程不是已經(jīng)獲取寫鎖的線程,則當(dāng)前線程進(jìn)入等待狀態(tài)。
寫鎖的獲取
寫鎖的獲取入口通過WriteLock的lock方法
public void lock() { sync.acquire(1); }
Sync的acquire(1)方法 定義在AQS中
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
tryAcquire(arg) 方法除了重入方法外,還增加了是否存在讀鎖的判斷,如果讀鎖存在、則不能獲取寫鎖。原因在于寫操作要對所有的讀操作的可見性。
protected final boolean tryAcquire(int acquires) { Thread current = Thread.currentThread(); // 獲取同步狀態(tài) int c = getState(); // 獲取寫鎖的獲取次數(shù) int w = exclusiveCount(c); // 已有線程獲取鎖 if (c != 0) { // (Note: if c != 0 and w == 0 then shared count != 0) /** * w == 0 表示存在讀鎖(同步狀態(tài)不等于0說明已有線程獲取到鎖(讀/寫) * 而寫鎖狀態(tài)為0則說明不存在寫鎖,所以只能是讀鎖了) * current != getExclusiveOwnerThread()) 不是自己獲取的寫鎖 * * 如果存在讀鎖或者持有寫鎖的線程不是自己,直接返回false */ if (w == 0 || current != getExclusiveOwnerThread()) return false; // 如果獲取寫鎖的數(shù)量超過最大值65535 ,直接異常 if (w + exclusiveCount(acquires) > MAX_COUNT) throw new Error("Maximum lock count exceeded"); // Reentrant acquire // 設(shè)置共享狀態(tài) setState(c + acquires); return true; } /** * writerShouldBlock() 是否需要阻塞寫鎖,這里直接返回的是false * compareAndSetState(c, c + acquires) 設(shè)置寫鎖的狀態(tài) */ if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) return false; setExclusiveOwnerThread(current); return true; }
寫鎖的獲取基本和RenntrantLock 類似
判斷當(dāng)前是否有線程持有寫鎖(寫鎖的狀態(tài)是否為0)
寫鎖的狀態(tài)不為0,如果存在讀鎖或者寫鎖不是自己持有則直接返回fasle。
判斷申請寫鎖數(shù)量是否超標(biāo)(> 65535),超標(biāo)則直接異常,反之則設(shè)置共享狀態(tài)。
寫鎖狀態(tài)為0,如果寫鎖需要阻塞或者CAS設(shè)置共享狀態(tài)失敗,則直接返回false,否則獲取鎖成功,設(shè)置持鎖線程為自己。
來張圖加深下理解
寫鎖的釋放
寫鎖的釋放和ReentrantLock 極為相似, 每次釋放就是狀態(tài)減1 ,當(dāng)狀態(tài)為0表示釋放成功。
寫鎖釋放的入口WriteLock中的unlock方法
public void unlock() { sync.release(1) }
Sync 中release方法由AQS中實現(xiàn)的
public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; }
tryRelease(arg) 方法釋放共享狀態(tài),非常簡單就是共享狀態(tài)減1,為0表示釋放成功
protected final boolean tryRelease(int releases) { // 判斷鎖持有者是否是自己 if (!isHeldExclusively()) throw new IllegalMonitorStateException(); // 共享狀態(tài)值 - release int nextc = getState() - releases; // 判斷寫鎖數(shù)量是否為0 boolean free = exclusiveCount(nextc) == 0; if (free) setExclusiveOwnerThread(null); setState(nextc); return free; }
寫鎖的釋放很簡單
首先判斷鎖持有者不是自己則直接異常
是自己則將共享狀態(tài) -1
判斷寫鎖數(shù)量是否為0,如果為0將持有鎖線程變量設(shè)為null
設(shè)置共享狀態(tài)
來張圖加深下理解
五、讀鎖的獲取與釋放讀鎖為一個可重入的共享鎖,它能夠被多個線程同時持有,在沒有其他寫線程訪問時,讀鎖總是獲取成功,所需要的也就是(線程安全的)增加讀狀態(tài)。
讀鎖的獲取
讀鎖的獲取可以通過ReadLock.lock()方法。
public void lock() { //讀鎖是一個可重入共享鎖,委托給內(nèi)部類Sync實現(xiàn) sync.acquireShared(1); }
Sync的acquireShared(1)方法定義在AQS中
public final void acquireShared(int arg) { // AQS 中 嘗試獲取共享狀態(tài),如果共享狀態(tài)大于等于0則說明獲取鎖成功,否則加入同步隊列。 if (tryAcquireShared(arg) < 0) doAcquireShared(arg); }
tryAcquireShared(int unused)方法中,如果其他線程獲取了寫鎖,則讀鎖獲取失敗線程將進(jìn)入等待狀態(tài),如果當(dāng)前線程獲取寫鎖或者寫鎖未被獲取則利用CAS(線程安全的)增加同步狀態(tài),成功則獲取鎖。
protected final int tryAcquireShared(int unused) { Thread current = Thread.currentThread(); // 獲取共享狀態(tài) int c = getState(); // 判斷是否有寫鎖 && 持有寫鎖的線程是否是自己,為true直接返回-1 if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) return -1; //獲取共享資源的數(shù)量 int r = sharedCount(c); /** * readerShouldBlock():判斷鎖是否需要等待(公平鎖原則) * r < MAX_COUNT:判斷鎖的數(shù)量是否超過最大值65535 * compareAndSetState(c, c + SHARED_UNIT): 設(shè)置共享狀態(tài)(讀鎖狀態(tài)) */ if (!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) { // r==0 :當(dāng)前沒有任何線程獲取讀鎖 if (r == 0) { // 設(shè)置當(dāng)前線程為第一個獲取讀鎖的線程 firstReader = current; // 計數(shù)設(shè)置為1 firstReaderHoldCount = 1; } else if (firstReader == current) { // 表示重入鎖,在計數(shù)其上+1 firstReaderHoldCount++; } else { /** * HoldCounter 主要是一個類來記錄線程獲取鎖的數(shù)量 * cachedHoldCounter 緩存的是最后一個獲取鎖線程的HoldCounter對象 */ HoldCounter rh = cachedHoldCounter; // 如果緩存不存在,或者線程不是自己 if (rh == null || rh.tid != getThreadId(current)) // 從當(dāng)前線程本地變量ThreadLocalHoldCounter 中獲取HoldCounter 并賦值給 cachedHoldCounter, rh cachedHoldCounter = rh = readHolds.get(); // 如果緩存的HoldCounter 是當(dāng)前的線程的,且計數(shù)為0 else if (rh.count == 0) // 將rh 存到ThreadLocalHoldCounter 中,將計數(shù)+1 readHolds.set(rh); rh.count++; } return 1; } /** * 進(jìn)入fullTryAcquireShared(current) 條件 * 1: readerShouldBlock() = true * 2: r < MAX_COUNT = false 讀鎖達(dá)到最大 * 3: 設(shè)置共享狀態(tài)失敗 return fullTryAcquireShared(current); }
NonfairSync 中的 readerShouldBlock() 方法判斷當(dāng)前申請讀鎖的線程是否需要阻塞
final boolean readerShouldBlock() { return apparentlyFirstQueuedIsExclusive(); }
apparentlyFirstQueuedIsExclusive() 判斷同步隊列中老二節(jié)點(diǎn)是否是獨(dú)占式(獲取寫鎖請求)是返回ture 否則返回false
final boolean apparentlyFirstQueuedIsExclusive() { Node h, s; // 主要條件判斷下一個節(jié)點(diǎn)是否是獲取寫鎖線程在排隊 return (h = head) != null && (s = h.next) != null && !s.isShared() && s.thread != null; }
自旋來獲取讀鎖,個人感覺對tryAcquireShared(int unused) 方法獲取讀鎖失敗的一種補(bǔ)救,其實現(xiàn)邏輯基本相同。
final int fullTryAcquireShared(Thread current) { // 線程內(nèi)部計數(shù)器 HoldCounter rh = null; // 自旋 for (;;) { // 獲取共享狀態(tài) int c = getState(); /** * exclusiveCount(c) !=0:存在獨(dú)占鎖(寫鎖) * getExclusiveOwnerThread() != current 判斷是否是自己持有寫鎖 * 再次是寫鎖是否是自己 */ if (exclusiveCount(c) != 0) { if (getExclusiveOwnerThread() != current) return -1; } else if (readerShouldBlock()) {//判斷讀鎖是否需要阻塞 // 如果需要阻塞,表示除了當(dāng)前線程持有寫鎖外,還有其他線程在等待獲取寫鎖,故,即使申請讀鎖的線程已經(jīng)持有寫鎖(寫鎖內(nèi)部再次申請讀鎖,俗稱鎖降級)還是會失敗,因為有其他線程也在申請寫鎖,此時,只能結(jié)束本次申請讀鎖的請求,轉(zhuǎn)而去排隊,否則,將造成死鎖 if (firstReader == current) { // assert firstReaderHoldCount > 0; } else { // 到這里其實就寫鎖的一個讓步, 清楚HoldCounter 緩存 if (rh == null) { rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) { rh = readHolds.get(); if (rh.count == 0) readHolds.remove(); } } if (rh.count == 0) return -1; } } // 下面邏輯和tryAcquireShared(int unused) 基本相同不再解釋了 if (sharedCount(c) == MAX_COUNT) throw new Error("Maximum lock count exceeded"); if (compareAndSetState(c, c + SHARED_UNIT)) { if (sharedCount(c) == 0) { firstReader = current; firstReaderHoldCount = 1; } else if (firstReader == current) { firstReaderHoldCount++; } else { if (rh == null) rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) rh = readHolds.get(); else if (rh.count == 0) readHolds.set(rh); rh.count++; cachedHoldCounter = rh; // cache for release } return 1; } } }
讀鎖的獲取稍微有點(diǎn)復(fù)雜,整個過程如下
如果其他線程獲取了寫鎖、則獲取讀鎖失敗。
如果當(dāng)前線程獲取到了寫鎖或者寫鎖未被獲取則利用CAS(線程安全的)增加讀鎖狀態(tài)
否則 fullTryAcquireShared(Thread current) 自旋方式再次來嘗試獲取。
讀鎖獲取流程圖如下
讀鎖的釋放
讀鎖的釋放通過ReadLock的unlock()方式釋放的。
public void unlock() { sync.releaseShared(1); }
Sync的releaseShared(1)同樣定義在AQS中
public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false; }
調(diào)用tryReleaseShared(int unused) 方法來釋放共享狀態(tài)。
protected final boolean tryReleaseShared(int unused) { Thread current = Thread.currentThread(); //判斷當(dāng)前線程釋放是第一個獲取讀鎖的線程 if (firstReader == current) { // assert firstReaderHoldCount > 0; // 判斷獲取鎖的次數(shù)釋放為1,如果為1說明沒有重入情況,直接釋放firstReader = null;否則將該線程持有鎖的數(shù)量 -1 if (firstReaderHoldCount == 1) firstReader = null; else firstReaderHoldCount--; } else { // 如果當(dāng)前線程不是第一個獲取讀鎖的線程。 // 獲取緩存中的HoldCounter HoldCounter rh = cachedHoldCounter; // 如果緩存中的HoldCounter 不屬于當(dāng)前線程則獲取當(dāng)前線程的HoldCounter。 if (rh == null || rh.tid != getThreadId(current)) rh = readHolds.get(); int count = rh.count; if (count <= 1) { // 如果線程持有鎖的數(shù)量小于等1 直接刪除HoldCounter readHolds.remove(); if (count <= 0) throw unmatchedUnlockException(); } // 持有鎖數(shù)量大于1 則執(zhí)行 - 1操作 --rh.count; } // 自旋釋放同步狀態(tài) for (;;) { int c = getState(); int nextc = c - SHARED_UNIT; if (compareAndSetState(c, nextc)) // Releasing the read lock has no effect on readers, // but it may allow waiting writers to proceed if // both read and write locks are now free. return nextc == 0; } }
鎖的釋放比較簡單,
首先看當(dāng)前線程是否是第一個獲取讀鎖的線程,如果是并且沒有發(fā)生重入,則將首次獲取讀鎖變量設(shè)為null, 如果發(fā)生重入,則將首次獲取讀鎖計數(shù)器 -1
其次 查看緩存中計數(shù)器是否為空或者是否是當(dāng)前線程,如果為空或者不是則獲取當(dāng)前線程的計數(shù)器,如果計數(shù)器個數(shù)小于等1, 從ThreadLocl 中刪除計數(shù)器,并計數(shù)器值-1,如果小于等于0異常 。
最后自旋修改同步狀態(tài)。
讀鎖釋放流程圖如下
六、總結(jié)通過上面的源碼分析,我們來總結(jié)下:
在線程持有讀鎖的情況下,該線程不能取得寫鎖(為了保證寫操作對后續(xù)所有的讀操作保持可見性)。
在線程持有寫鎖的情況下,該線程可以繼續(xù)獲取讀鎖(獲取讀鎖時如果發(fā)現(xiàn)寫鎖被占用,只有寫鎖沒有被當(dāng)前線程占用的情況才會獲取失?。?。
仔細(xì)想想,這個設(shè)計是合理的:因為當(dāng)線程獲取讀鎖的時候,可能有其他線程同時也在持有讀鎖,因此不能把獲取讀鎖的線程“升級”為寫鎖;而對于獲得寫鎖的線程,它一定獨(dú)占了讀寫鎖,因此可以繼續(xù)讓它獲取讀鎖,當(dāng)它同時獲取了寫鎖和讀鎖后,還可以先釋放寫鎖繼續(xù)持有讀鎖,這樣一個寫鎖就“降級”為了讀
一個線程要想同時持有寫鎖和讀鎖,必須先獲取寫鎖再獲取讀鎖;寫鎖可以“降級”為讀鎖;讀鎖不能“升級”為寫鎖。
因技術(shù)水平有限,如有不對的地方,歡迎拍磚
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/77621.html
摘要:所以就有了讀寫鎖。只要沒有,讀取鎖可以由多個線程同時保持。其讀寫鎖為兩個內(nèi)部類都實現(xiàn)了接口。讀寫鎖同樣依賴自定義同步器來實現(xiàn)同步狀態(tài)的,而讀寫狀態(tài)就是其自定義同步器的狀態(tài)。判斷申請寫鎖數(shù)量是否超標(biāo)超標(biāo)則直接異常,反之則設(shè)置共享狀態(tài)。 一、寫在前面 在上篇我們聊到了可重入鎖(排它鎖)ReentrantLcok ,具體參見《J.U.C|可重入鎖ReentrantLock》 Reentra...
摘要:關(guān)于,最后有兩點(diǎn)規(guī)律需要注意當(dāng)?shù)牡却犃嘘犑捉Y(jié)點(diǎn)是共享結(jié)點(diǎn),說明當(dāng)前寫鎖被占用,當(dāng)寫鎖釋放時,會以傳播的方式喚醒頭結(jié)點(diǎn)之后緊鄰的各個共享結(jié)點(diǎn)。當(dāng)?shù)牡却犃嘘犑捉Y(jié)點(diǎn)是獨(dú)占結(jié)點(diǎn),說明當(dāng)前讀鎖被使用,當(dāng)讀鎖釋放歸零后,會喚醒隊首的獨(dú)占結(jié)點(diǎn)。 showImg(https://segmentfault.com/img/remote/1460000016012293); 本文首發(fā)于一世流云的專欄:...
摘要:不同的是它還多了內(nèi)部類和內(nèi)部類,以及讀寫對應(yīng)的成員變量和方法。另外是給和內(nèi)部類使用的。內(nèi)部類前面說到的操作是分配到里面執(zhí)行的。他們都是接口的實現(xiàn),所以其實最像應(yīng)該是這個兩個內(nèi)部類。而且大體上也沒什么差異,也是用的內(nèi)部類。 之前講了《AQS源碼閱讀》和《ReentrantLock源碼閱讀》,本次將延續(xù)閱讀下ReentrantReadWriteLock,建議沒看過之前兩篇文章的,先大概了解...
摘要:類顧名思義是一種讀寫鎖它是接口的直接實現(xiàn)該類在內(nèi)部實現(xiàn)了具體獨(dú)占鎖特點(diǎn)的寫鎖以及具有共享鎖特點(diǎn)的讀鎖和一樣類也是通過定義內(nèi)部類實現(xiàn)框架的來實現(xiàn)獨(dú)占共享的功能屬于排他鎖這些鎖在同一時刻只允許一個線程進(jìn)行訪問但是在大多數(shù)場景下大部分時間都是提供 ReentrantReadWriteLock 類, 顧名思義, 是一種讀寫鎖, 它是 ReadWriteLock 接口的直接實現(xiàn), 該類在內(nèi)部實現(xiàn)...
摘要:我們知道,的作用其實是對類的和的增強(qiáng),是為了讓線程在指定對象上等待,是一種線程之間進(jìn)行協(xié)調(diào)的工具。當(dāng)線程調(diào)用對象的方法時,必須拿到和這個對象關(guān)聯(lián)的鎖。 showImg(https://segmentfault.com/img/remote/1460000016012566); 本文首發(fā)于一世流云的專欄:https://segmentfault.com/blog... 一、Reentr...
閱讀 1354·2021-09-10 10:51
閱讀 2829·2019-08-30 15:54
閱讀 3367·2019-08-29 17:11
閱讀 926·2019-08-29 16:44
閱讀 1391·2019-08-29 13:47
閱讀 1086·2019-08-29 13:47
閱讀 1485·2019-08-29 12:23
閱讀 1038·2019-08-28 18:18