ReadWriteLock

ReadWriteLock

從這一節(jié)開始介紹鎖里面的最后一個(gè)工具:讀寫鎖(ReadWriteLock)稽煤。

ReentrantLock 實(shí)現(xiàn)了標(biāo)準(zhǔn)的互斥操作噪伊,也就是一次只能有一個(gè)線程持有鎖,也即所謂獨(dú)占鎖的概念磁携。前面的章節(jié)中一直在強(qiáng)調(diào)這個(gè)特點(diǎn)褒侧。顯然這個(gè)特點(diǎn)在一定程度上面減低了吞吐量,實(shí)際上獨(dú)占鎖是一種保守的鎖策略谊迄,在這種情況下任何“讀/讀”闷供,“寫/讀”,“寫/寫”操作都不能同時(shí)發(fā)生统诺。但是同樣需要強(qiáng)調(diào)的一個(gè)概念是歪脏,鎖是有一定的開銷的,當(dāng)并發(fā)比較大的時(shí)候粮呢,鎖的開銷就比較客觀了婿失。所以如果可能的話就盡量少用鎖,非要用鎖的話就嘗試看能否改造為讀寫鎖啄寡。

ReadWriteLock描述的是:一個(gè)資源能夠被多個(gè)讀線程訪問豪硅,或者被一個(gè)寫線程訪問,但是不能同時(shí)存在讀寫線程挺物。也就是說讀寫鎖使用的場合是一個(gè)共享資源被大量讀取操作懒浮,而只有少量的寫操作(修改數(shù)據(jù))。清單1描述了ReadWriteLock的API识藤。

清單1 ReadWriteLock 接口

public interface ReadWriteLock {
? Lock readLock();
? Lock writeLock();
}

清單1描述的ReadWriteLock結(jié)構(gòu)砚著,這里需要說明的是ReadWriteLock并不是Lock的子接口,只不過ReadWriteLock借助Lock來實(shí)現(xiàn)讀寫兩個(gè)視角痴昧。在ReadWriteLock中每次讀取共享數(shù)據(jù)就需要讀取鎖赖草,當(dāng)需要修改共享數(shù)據(jù)時(shí)就需要寫入鎖〖舾觯看起來好像是兩個(gè)鎖秧骑,但其實(shí)不盡然,在下一節(jié)中的分析中會解釋這點(diǎn)奧秘扣囊。

在JDK 6里面ReadWriteLock的實(shí)現(xiàn)是ReentrantReadWriteLock乎折。

清單2 SimpleConcurrentMap

package xylz.study.concurrency.lock;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class SimpleConcurrentMap<K, V> implements Map<K, V> {

? final ReadWriteLock lock = new ReentrantReadWriteLock();

? final Lock r = lock.readLock();

? final Lock w = lock.writeLock();

? final Map<K, V> map;

? public SimpleConcurrentMap(Map<K, V> map) {
? this.map = map;
? if (map == null) throw new NullPointerException();
? }

? public void clear() {
? w.lock();
? try {
? map.clear();
? } finally {
? w.unlock();
? }
? }

? public boolean containsKey(Object key) {
? r.lock();
? try {
? return map.containsKey(key);
? } finally {
? r.unlock();
? }
? }

? public boolean containsValue(Object value) {
? r.lock();
? try {
? return map.containsValue(value);
? } finally {
? r.unlock();
? }
? }

? public Set<java.util.Map.Entry<K, V>> entrySet() {
? throw new UnsupportedOperationException();
? }

? public V get(Object key) {
? r.lock();
? try {
? return map.get(key);
? } finally {
? r.unlock();
? }
? }

? public boolean isEmpty() {
? r.lock();
? try {
? return map.isEmpty();
? } finally {
? r.unlock();
? }
? }

? public Set<K> keySet() {
? r.lock();
? try {
? return new HashSet<K>(map.keySet());
? } finally {
? r.unlock();
? }
? }

? public V put(K key, V value) {
? w.lock();
? try {
? return map.put(key, value);
? } finally {
? w.unlock();
? }
? }

? public void putAll(Map<? extends K, ? extends V> m) {
? w.lock();
? try {
? map.putAll(m);
? } finally {
? w.unlock();
? }
? }

? public V remove(Object key) {
? w.lock();
? try {
? return map.remove(key);
? } finally {
? w.unlock();
? }
? }

? public int size() {
? r.lock();
? try {
? return map.size();
? } finally {
? r.unlock();
? }
? }

? public Collection<V> values() {
? r.lock();
? try {
? return new ArrayList<V>(map.values());
? } finally {
? r.unlock();
? }
? }

}

清單2描述的是用讀寫鎖實(shí)現(xiàn)的一個(gè)線程安全的Map。其中需要特別說明的是并沒有實(shí)現(xiàn)entrySet()方法侵歇,這是因?yàn)閷?shí)現(xiàn)這個(gè)方法比較復(fù)雜骂澄,在后面章節(jié)中講到ConcurrentHashMap的時(shí)候會具體談這些細(xì)節(jié)。另外這里keySet()和values()也沒有直接返回Map的視圖惕虑,而是一個(gè)映射原有元素的新視圖坟冲,其實(shí)這個(gè)entrySet()一樣磨镶,是為了保護(hù)原始Map的數(shù)據(jù)邏輯,防止不正確的修改導(dǎo)致原始Map發(fā)生數(shù)據(jù)錯(cuò)誤健提。特別說明的是在沒有特別需求的情況下沒有必要按照清單2寫一個(gè)線程安全的Map實(shí)現(xiàn)琳猫,因?yàn)镃oncurrentHashMap已經(jīng)完成了此操作。

ReadWriteLock需要嚴(yán)格區(qū)分讀寫操作私痹,如果讀操作使用了寫入鎖脐嫂,那么降低讀操作的吞吐量,如果寫操作使用了讀取鎖紊遵,那么就可能發(fā)生數(shù)據(jù)錯(cuò)誤账千。

另外ReentrantReadWriteLock還有以下幾個(gè)特性:

  • 公平性非公平鎖(默認(rèn)) 這個(gè)和獨(dú)占鎖的非公平性一樣,由于讀線程之間沒有鎖競爭暗膜,所以讀操作沒有公平性和非公平性匀奏,寫操作時(shí),由于寫操作可能立即獲取到鎖学搜,所以會推遲一個(gè)或多個(gè)讀操作或者寫操作攒射。因此非公平鎖的吞吐量要高于公平鎖。公平鎖 利用AQS的CLH隊(duì)列恒水,釋放當(dāng)前保持的鎖(讀鎖或者寫鎖)時(shí)会放,優(yōu)先為等待時(shí)間最長的那個(gè)寫線程分配寫入鎖,當(dāng)前前提是寫線程的等待時(shí)間要比所有讀線程的等待時(shí)間要長钉凌。同樣一個(gè)線程持有寫入鎖或者有一個(gè)寫線程已經(jīng)在等待了咧最,那么試圖獲取公平鎖的(非重入)所有線程(包括讀寫線程)都將被阻塞,直到最先的寫線程釋放鎖御雕。如果讀線程的等待時(shí)間比寫線程的等待時(shí)間還有長矢沿,那么一旦上一個(gè)寫線程釋放鎖,這一組讀線程將獲取鎖酸纲。
  • 重入性讀寫鎖允許讀線程和寫線程按照請求鎖的順序重新獲取讀取鎖或者寫入鎖捣鲸。當(dāng)然了只有寫線程釋放了鎖,讀線程才能獲取重入鎖闽坡。寫線程獲取寫入鎖后可以再次獲取讀取鎖栽惶,但是讀線程獲取讀取鎖后卻不能獲取寫入鎖。另外讀寫鎖最多支持65535個(gè)遞歸寫入鎖和65535個(gè)遞歸讀取鎖疾嗅。
  • 鎖降級寫線程獲取寫入鎖后可以獲取讀取鎖外厂,然后釋放寫入鎖,這樣就從寫入鎖變成了讀取鎖代承,從而實(shí)現(xiàn)鎖降級的特性汁蝶。
  • 鎖升級
    • 讀取鎖是不能直接升級為寫入鎖的。因?yàn)楂@取一個(gè)寫入鎖需要釋放所有讀取鎖论悴,所以如果有兩個(gè)讀取鎖視圖獲取寫入鎖而都不釋放讀取鎖時(shí)就會發(fā)生死鎖掖棉。
  • 鎖獲取中斷讀取鎖和寫入鎖都支持獲取鎖期間被中斷墓律。這個(gè)和獨(dú)占鎖一致。
  • 條件變量寫入鎖提供了條件變量(Condition)的支持幔亥,這個(gè)和獨(dú)占鎖一致耻讽,但是讀取鎖卻不允許獲取條件變量,將得到一個(gè)UnsupportedOperationException異常紫谷。
  • 重入數(shù)讀取鎖和寫入鎖的數(shù)量最大分別只能是65535(包括重入數(shù))齐饮。這在下節(jié)中有介紹捐寥。

上面幾個(gè)特性對讀寫鎖的理解很有幫助笤昨,而且也是必要的,另外在下一節(jié)中講ReadWriteLock的實(shí)現(xiàn)會用到這些知識的握恳。

這一節(jié)主要是談?wù)勛x寫鎖的實(shí)現(xiàn)瞒窒。

上一節(jié)中提到,ReadWriteLock看起來有兩個(gè)鎖:readLock/writeLock乡洼。如果真的是兩個(gè)鎖的話崇裁,它們之間又是如何相互影響的呢?

事實(shí)上在ReentrantReadWriteLock里鎖的實(shí)現(xiàn)是靠java.util.concurrent.locks.ReentrantReadWriteLock.Sync完成的束昵。這個(gè)類看起來比較眼熟拔稳,實(shí)際上它是AQS的一個(gè)子類,這中類似的結(jié)構(gòu)在CountDownLatch锹雏、ReentrantLock巴比、Semaphore里面都存在。同樣它也有兩種實(shí)現(xiàn):公平鎖和非公平鎖礁遵,也就是java.util.concurrent.locks.ReentrantReadWriteLock.FairSync和java.util.concurrent.locks.ReentrantReadWriteLock.NonfairSync轻绞。這里暫且不提。

在ReentrantReadWriteLock里面的鎖主體就是一個(gè)Sync佣耐,也就是上面提到的FairSync或者NonfairSync政勃,所以說實(shí)際上只有一個(gè)鎖,只是在獲取讀取鎖和寫入鎖的方式上不一樣兼砖,所以前面才有讀寫鎖是獨(dú)占鎖的兩個(gè)不同視圖一說奸远。

ReentrantReadWriteLock里面有兩個(gè)類:ReadLock/WriteLock,這兩個(gè)類都是Lock的實(shí)現(xiàn)讽挟。

清單1 ReadLock 片段

public static class ReadLock implements Lock, java.io.Serializable {
? private final Sync sync;

? protected ReadLock(ReentrantReadWriteLock lock) {
? sync = lock.sync;
? }

? public void lock() {
? sync.acquireShared(1);
? }

? public void lockInterruptibly() throws InterruptedException {
? sync.acquireSharedInterruptibly(1);
? }

? public boolean tryLock() {
? return sync.tryReadLock();
? }

? public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
? return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
? }

? public void unlock() {
? sync.releaseShared(1);
? }

? public Condition newCondition() {
? throw new UnsupportedOperationException();
? }

}

清單2 WriteLock 片段

public static class WriteLock implements Lock, java.io.Serializable {
? private final Sync sync;
? protected WriteLock(ReentrantReadWriteLock lock) {
? sync = lock.sync;
? }
? public void lock() {
? sync.acquire(1);
? }

? public void lockInterruptibly() throws InterruptedException {
? sync.acquireInterruptibly(1);
? }

? public boolean tryLock( ) {
? return sync.tryWriteLock();
? }

? public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
? return sync.tryAcquireNanos(1, unit.toNanos(timeout));
? }

? public void unlock() {
? sync.release(1);
? }

? public Condition newCondition() {
? return sync.newCondition();
? }

? public boolean isHeldByCurrentThread() {
? return sync.isHeldExclusively();
? }

? public int getHoldCount() {
? return sync.getWriteHoldCount();
? }
}

清單1描述的是讀鎖的實(shí)現(xiàn)然走,清單2描述的是寫鎖的實(shí)現(xiàn)。顯然WriteLock就是一個(gè)獨(dú)占鎖戏挡,這和ReentrantLock里面的實(shí)現(xiàn)幾乎相同芍瑞,都是使用了AQS的acquire/release操作。當(dāng)然了在內(nèi)部處理方式上與ReentrantLock還是有一點(diǎn)不同的褐墅。對比清單1和清單2可以看到拆檬,ReadLock獲取的是共享鎖洪己,WriteLock獲取的是獨(dú)占鎖。

在AQS章節(jié)中介紹到AQS中有一個(gè)state字段(int類型竟贯,32位)用來描述有多少線程獲持有鎖答捕。在獨(dú)占鎖的時(shí)代這個(gè)值通常是0或者1(如果是重入的就是重入的次數(shù)),在共享鎖的時(shí)代就是持有鎖的數(shù)量屑那。在上一節(jié)中談到拱镐,ReadWriteLock的讀、寫鎖是相關(guān)但是又不一致的持际,所以需要兩個(gè)數(shù)來描述讀鎖(共享鎖)和寫鎖(獨(dú)占鎖)的數(shù)量沃琅。顯然現(xiàn)在一個(gè)state就不夠用了。于是在ReentrantReadWrilteLock里面將這個(gè)字段一分為二蜘欲,高位16位表示共享鎖的數(shù)量益眉,低位16位表示獨(dú)占鎖的數(shù)量(或者重入數(shù)量)。2^16-1=65536姥份,這就是上節(jié)中提到的為什么共享鎖和獨(dú)占鎖的數(shù)量最大只能是65535的原因了郭脂。

有了上面的知識后再來分析讀寫鎖的獲取和釋放就容易多了。

清單3 寫入鎖獲取片段

protected final boolean tryAcquire(int acquires) {
? Thread current = Thread.currentThread();
? int c = getState();
? int w = exclusiveCount(c);
? if (c != 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;
}

清單3 是寫入鎖獲取的邏輯片段澈歉,整個(gè)工作流程是這樣的:

    1. 持有鎖線程數(shù)非0(c=getState()不為0)展鸡,如果寫線程數(shù)(w)為0(那么讀線程數(shù)就不為0)或者獨(dú)占鎖線程(持有鎖的線程)不是當(dāng)前線程就返回失敗,或者寫入鎖的數(shù)量(其實(shí)是重入數(shù))大于65535就拋出一個(gè)Error異常埃难。否則進(jìn)行2莹弊。
    2. 如果當(dāng)且寫線程數(shù)位0(那么讀線程也應(yīng)該為0,因?yàn)椴襟E1已經(jīng)處理c!=0的情況)凯砍,并且當(dāng)前線程需要阻塞那么就返回失斚渌丁;如果增加寫線程數(shù)失敗也返回失敗悟衩。否則進(jìn)行3剧罩。
    3. 設(shè)置獨(dú)占線程(寫線程)為當(dāng)前線程,返回true座泳。

清單3 中 exclusiveCount(c)就是獲取寫線程數(shù)(包括重入數(shù))惠昔,也就是state的低16位值。另外這里有一段邏輯是當(dāng)前寫線程是否需要阻塞writerShouldBlock(current)挑势。清單4 和清單5 就是公平鎖和非公平鎖中是否需要阻塞的片段镇防。很顯然對于非公平鎖而言總是不阻塞當(dāng)前線程,而對于公平鎖而言如果AQS隊(duì)列不為空或者當(dāng)前線程不是在AQS的隊(duì)列頭那么就阻塞線程潮饱,直到隊(duì)列前面的線程處理完鎖邏輯来氧。

清單4 公平讀寫鎖寫線程是否阻塞

final boolean writerShouldBlock(Thread current) {
? return !isFirst(current);
}

*清單5 非公平讀寫鎖寫線程是否阻塞*

final boolean writerShouldBlock(Thread current) {
? return false;
}

寫入鎖的獲取邏輯清楚后,釋放鎖就比較簡單了。清單6 描述的寫入鎖釋放邏輯片段啦扬,其實(shí)就是檢測下剩下的寫入鎖數(shù)量中狂,如果是0就將獨(dú)占鎖線程清空(意味著沒有線程獲取鎖),否則就是說當(dāng)前是重入鎖的一次釋放扑毡,所以不能將獨(dú)占鎖線程清空胃榕。然后將剩余線程狀態(tài)數(shù)寫回AQS。

清單6 寫入鎖釋放邏輯片段

protected final boolean tryRelease(int releases) {
? int nextc = getState() - releases;
? if (Thread.currentThread() != getExclusiveOwnerThread())
? throw new IllegalMonitorStateException();
? if (exclusiveCount(nextc) == 0) {
? setExclusiveOwnerThread(null);
? setState(nextc);
? return true;
? } else {
? setState(nextc);
? return false;
? }
}

清單3~6 描述的寫入鎖的獲取釋放過程勋又。讀取鎖的獲取和釋放過程要稍微復(fù)雜些。 清單7描述的是讀取鎖的獲取過程换帜。

清單7 讀取鎖獲取過程片段

protected final int tryAcquireShared(int unused) {
? Thread current = Thread.currentThread();
? int c = getState();
? if (exclusiveCount(c) != 0 &&
? getExclusiveOwnerThread() != current)
? return -1;
? if (sharedCount(c) == MAX_COUNT)
? throw new Error("Maximum lock count exceeded");
? if (!readerShouldBlock(current) &&
? compareAndSetState(c, c + SHARED_UNIT)) {
? HoldCounter rh = cachedHoldCounter;
? if (rh == null || rh.tid != current.getId())
? cachedHoldCounter = rh = readHolds.get();
? rh.count++;
? return 1;
? }
? return fullTryAcquireShared(current);
}

final int fullTryAcquireShared(Thread current) {
? HoldCounter rh = cachedHoldCounter;
? if (rh == null || rh.tid != current.getId())
? rh = readHolds.get();
? for (;;) {
? int c = getState();
? int w = exclusiveCount(c);
? if ((w != 0 && getExclusiveOwnerThread() != current) ||
? ((rh.count | w) == 0 && readerShouldBlock(current)))
? return -1;
? if (sharedCount(c) == MAX_COUNT)
? throw new Error("Maximum lock count exceeded");
? if (compareAndSetState(c, c + SHARED_UNIT)) {
? cachedHoldCounter = rh; // cache for release
? rh.count++;
? return 1;
? }
? }
}

讀取鎖獲取的過程是這樣的:

    1. 如果寫線程持有鎖(也就是獨(dú)占鎖數(shù)量不為0)跳座,并且獨(dú)占線程不是當(dāng)前線程您朽,那么就返回失敗。因?yàn)樵试S寫入線程獲取鎖的同時(shí)獲取讀取鎖哗总。否則進(jìn)行2几颜。
    2. 如果讀線程請求鎖數(shù)量達(dá)到了65535(包括重入鎖),那么就跑出一個(gè)錯(cuò)誤Error讯屈,否則進(jìn)行3蛋哭。
    3. 如果讀線程不用等待(實(shí)際上是是否需要公平鎖),并且增加讀取鎖狀態(tài)數(shù)成功涮母,那么就返回成功谆趾,否則進(jìn)行4。
    4. 步驟3失敗的原因是CAS操作修改狀態(tài)數(shù)失敗叛本,那么就需要循環(huán)不斷嘗試去修改狀態(tài)直到成功或者鎖被寫入線程占有沪蓬。實(shí)際上是過程3的不斷嘗試直到CAS計(jì)數(shù)成功或者被寫入線程占有鎖。

在清單7 中有一個(gè)對象HoldCounter来候,這里暫且不提這是什么結(jié)構(gòu)和為什么存在這樣一個(gè)結(jié)構(gòu)跷叉。

接下來根據(jù)清單8 我們來看如何釋放一個(gè)讀取鎖。同樣先不理HoldCounter,關(guān)鍵的在于for循環(huán)里面云挟,其實(shí)就是一個(gè)不斷嘗試的CAS操作峡眶,直到修改狀態(tài)成功。前面說過state的高16位描述的共享鎖(讀取鎖)的數(shù)量植锉,所以每次都需要減去2^16辫樱,這樣就相當(dāng)于讀取鎖數(shù)量減1。實(shí)際上SHARED_UNIT=1<<16俊庇。

清單8 讀取鎖釋放過程

protected final boolean tryReleaseShared(int unused) {
? HoldCounter rh = cachedHoldCounter;
? Thread current = Thread.currentThread();
? if (rh == null || rh.tid != current.getId())
? rh = readHolds.get();
? if (rh.tryDecrement() <= 0)
? throw new IllegalMonitorStateException();
? for (;;) {
? int c = getState();
? int nextc = c - SHARED_UNIT;
? if (compareAndSetState(c, nextc))
? return nextc == 0;
? }
}

好了狮暑,現(xiàn)在回頭看HoldCounter到底是一個(gè)什么東西。首先我們可以看到只有在獲取共享鎖(讀取鎖)的時(shí)候加1辉饱,也只有在釋放共享鎖的時(shí)候減1有作用搬男,并且在釋放鎖的時(shí)候拋出了一個(gè)IllegalMonitorStateException異常。而我們知道IllegalMonitorStateException通常描述的是一個(gè)線程操作一個(gè)不屬于自己的監(jiān)視器對象的引發(fā)的異常彭沼。也就是說這里的意思是一個(gè)線程釋放了一個(gè)不屬于自己或者不存在的共享鎖缔逛。

前面的章節(jié)中一再強(qiáng)調(diào),對于共享鎖姓惑,其實(shí)并不是鎖的概念褐奴,更像是計(jì)數(shù)器的概念。一個(gè)共享鎖就相對于一次計(jì)數(shù)器操作于毙,一次獲取共享鎖相當(dāng)于計(jì)數(shù)器加1敦冬,釋放一個(gè)共享鎖就相當(dāng)于計(jì)數(shù)器減1。顯然只有線程持有了共享鎖(也就是當(dāng)前線程攜帶一個(gè)計(jì)數(shù)器唯沮,描述自己持有多少個(gè)共享鎖或者多重共享鎖)脖旱,才能釋放一個(gè)共享鎖。否則一個(gè)沒有獲取共享鎖的線程調(diào)用一次釋放操作就會導(dǎo)致讀寫鎖的state(持有鎖的線程數(shù)介蛉,包括重入數(shù))錯(cuò)誤萌庆。

明白了HoldCounter的作用后我們就可以猜到它的作用其實(shí)就是當(dāng)前線程持有共享鎖(讀取鎖)的數(shù)量,包括重入的數(shù)量币旧。那么這個(gè)數(shù)量就必須和線程綁定在一起践险。

在Java里面將一個(gè)對象和線程綁定在一起,就只有ThreadLocal才能實(shí)現(xiàn)了佳恬。所以毫無疑問HoldCounter就應(yīng)該是綁定到線程上的一個(gè)計(jì)數(shù)器捏境。

清單9 線程持有讀取鎖數(shù)量的計(jì)數(shù)器

static final class HoldCounter {
? int count;
? final long tid = Thread.currentThread().getId();
? int tryDecrement() {
? int c = count;
? if (c > 0)
? count = c - 1;
? return c;
? }
}

static final class ThreadLocalHoldCounter
? extends ThreadLocal<HoldCounter> {
? public HoldCounter initialValue() {
? return new HoldCounter();
? }
}

清單9 描述的是線程持有讀取鎖數(shù)量的計(jì)數(shù)器』俅校可以看到這里使用ThreadLocal將HoldCounter綁定到當(dāng)前線程上垫言,同時(shí)HoldCounter也持有線程Id,這樣在釋放鎖的時(shí)候才能知道ReadWriteLock里面緩存的上一個(gè)讀取線程(cachedHoldCounter)是否是當(dāng)前線程倾剿。這樣做的好處是可以減少ThreadLocal.get()的次數(shù)筷频,因?yàn)檫@也是一個(gè)耗時(shí)操作蚌成。需要說明的是這樣HoldCounter綁定線程id而不綁定線程對象的原因是避免HoldCounter和ThreadLocal互相綁定而GC難以釋放它們(盡管GC能夠智能的發(fā)現(xiàn)這種引用而回收它們,但是這需要一定的代價(jià))凛捏,所以其實(shí)這樣做只是為了幫助GC快速回收對象而已担忧。

除了readLock()和writeLock()外,Lock對象還允許tryLock()坯癣,那么ReadLock和WriteLock的tryLock()不一樣瓶盛。清單10 和清單11 分別描述了讀取鎖的tryLock()和寫入鎖的tryLock()。

讀取鎖tryLock()也就是tryReadLock()成功的條件是:沒有寫入鎖或者寫入鎖是當(dāng)前線程示罗,并且讀線程共享鎖數(shù)量沒有超過65535個(gè)惩猫。

寫入鎖tryLock()也就是tryWriteLock()成功的條件是: 沒有寫入鎖或者寫入鎖是當(dāng)前線程,并且嘗試一次修改state成功蚜点。

清單10 讀取鎖的tryLock()

final boolean tryReadLock() {
? Thread current = Thread.currentThread();
? for (;;) {
? int c = getState();
? if (exclusiveCount(c) != 0 &&
? getExclusiveOwnerThread() != current)
? return false;
? if (sharedCount(c) == MAX_COUNT)
? throw new Error("Maximum lock count exceeded");
? if (compareAndSetState(c, c + SHARED_UNIT)) {
? HoldCounter rh = cachedHoldCounter;
? if (rh == null || rh.tid != current.getId())
? cachedHoldCounter = rh = readHolds.get();
? rh.count++;
? return true;
? }
? }
}

清單11 寫入鎖的tryLock()

final boolean tryWriteLock() {
? Thread current = Thread.currentThread();
? int c = getState();
? if (c != 0) {
? int w = exclusiveCount(c);
? if (w == 0 ||current != getExclusiveOwnerThread())
? return false;
? if (w == MAX_COUNT)
? throw new Error("Maximum lock count exceeded");
? }
? if (!compareAndSetState(c, c + 1))
? return false;
? setExclusiveOwnerThread(current);
? return true;
}

整個(gè)讀寫鎖的邏輯大概就這么多轧房,其實(shí)真正研究起來也不是很復(fù)雜,真正復(fù)雜的東西都在AQS里面绍绘。

鎖部分的原理和思想都介紹完了奶镶,下一節(jié)里面會對鎖機(jī)進(jìn)行小節(jié),并對線程并發(fā)也會有一些簡單的小節(jié)陪拘。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末厂镇,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子藻丢,更是在濱河造成了極大的恐慌剪撬,老刑警劉巖摄乒,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件悠反,死亡現(xiàn)場離奇詭異,居然都是意外死亡馍佑,警方通過查閱死者的電腦和手機(jī)斋否,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拭荤,“玉大人茵臭,你說我怎么就攤上這事【耸溃” “怎么了旦委?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長雏亚。 經(jīng)常有香客問我缨硝,道長,這世上最難降的妖魔是什么罢低? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任查辩,我火速辦了婚禮胖笛,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘宜岛。我一直安慰自己长踊,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布萍倡。 她就那樣靜靜地躺著身弊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪列敲。 梳的紋絲不亂的頭發(fā)上佑刷,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天,我揣著相機(jī)與錄音酿炸,去河邊找鬼瘫絮。 笑死,一個(gè)胖子當(dāng)著我的面吹牛填硕,可吹牛的內(nèi)容都是我干的麦萤。 我是一名探鬼主播,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼扁眯,長吁一口氣:“原來是場噩夢啊……” “哼壮莹!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起姻檀,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤命满,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后绣版,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體胶台,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年杂抽,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了诈唬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,615評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡缩麸,死狀恐怖铸磅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情杭朱,我是刑警寧澤阅仔,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站弧械,受9級特大地震影響八酒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜梦谜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一丘跌、第九天 我趴在偏房一處隱蔽的房頂上張望袭景。 院中可真熱鬧,春花似錦闭树、人聲如沸耸棒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽与殃。三九已至,卻和暖如春碍现,著一層夾襖步出監(jiān)牢的瞬間幅疼,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工昼接, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留爽篷,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓慢睡,卻偏偏與公主長得像逐工,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子漂辐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 1.解決信號量丟失和假喚醒 public class MyWaitNotify3{ MonitorObject m...
    Q羅閱讀 881評論 0 1
  • Java8張圖 11泪喊、字符串不變性 12、equals()方法髓涯、hashCode()方法的區(qū)別 13袒啼、...
    Miley_MOJIE閱讀 3,709評論 0 11
  • 從三月份找實(shí)習(xí)到現(xiàn)在,面了一些公司纬纪,掛了不少蚓再,但最終還是拿到小米、百度育八、阿里对途、京東、新浪髓棋、CVTE、樂視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,277評論 11 349
  • 一惶洲、框架圖: 從中可以看出: (01) ReentrantReadWriteLock實(shí)現(xiàn)了ReadWriteLoc...
    Stone_soul閱讀 436評論 0 0
  • 今天的運(yùn)動(dòng)是Apink《nonono》舞蹈按声。進(jìn)度比上學(xué)期慢很多,肢體也不是很協(xié)調(diào)恬吕。等學(xué)完签则,我發(fā)個(gè)視頻為證。 今天的...
    米迦勒的羽翼閱讀 218評論 0 0