從synchronized 到CAS 和 AQS徹底弄懂Java各種并發(fā)鎖

概述

Java 中的并發(fā)鎖大致分為隱式鎖和顯式鎖兩種。隱式鎖就是我們最常使用的 synchronized 關(guān)鍵字豹爹,顯式鎖主要包含兩個(gè)接口:Lock 和 ReadWriteLock裆悄,主要實(shí)現(xiàn)類分別為 ReentrantLock 和 ReentrantReadWriteLock,這兩個(gè)類都是基于 AQS(AbstractQueuedSynchronizer) 實(shí)現(xiàn)的臂聋。還有的地方將 CAS 也稱為一種鎖光稼,在包括 AQS 在內(nèi)的很多并發(fā)相關(guān)類中崖技,CAS 都扮演了很重要的角色。

我們只需要弄清楚 synchronized 和 AQS 的原理钟哥,再去理解并發(fā)鎖的性質(zhì)和局限就很簡(jiǎn)單了迎献。因此這篇文章重點(diǎn)放在原理上,對(duì)于使用和特點(diǎn)不會(huì)過多涉及腻贰。

概念辨析

下面是關(guān)于鎖的一些概念解釋吁恍,這些都是一些關(guān)于鎖的性質(zhì)的描述,并非具體實(shí)現(xiàn)播演。

悲觀鎖和樂觀鎖

悲觀鎖和獨(dú)占鎖是一個(gè)意思冀瓦,它假設(shè)一定會(huì)發(fā)生沖突,因此獲取到鎖之后會(huì)阻塞其他等待線程写烤。這么做的好處是簡(jiǎn)單安全翼闽,但是掛起線程和恢復(fù)線程都需要轉(zhuǎn)入內(nèi)核態(tài)進(jìn)行,這樣做會(huì)帶來很大的性能開銷洲炊。悲觀鎖的代表是 synchronized感局。然而在真實(shí)環(huán)境中,大部分時(shí)候都不會(huì)產(chǎn)生沖突暂衡。悲觀鎖會(huì)造成很大的浪費(fèi)询微。而樂觀鎖不一樣,它假設(shè)不會(huì)產(chǎn)生沖突狂巢,先去嘗試執(zhí)行某項(xiàng)操作插龄,失敗了再進(jìn)行其他處理(一般都是不斷循環(huán)重試)度帮。這種鎖不會(huì)阻塞其他的線程抹锄,也不涉及上下文切換孽拷,性能開銷小。代表實(shí)現(xiàn)是 CAS斩个。

公平鎖和非公平鎖

公平鎖是指各個(gè)線程在加鎖前先檢查有無排隊(duì)的線程胯杭,按排隊(duì)順序去獲得鎖。

非公平鎖是指線程加鎖前不考慮排隊(duì)問題萨驶,直接嘗試獲取鎖歉摧,獲取不到再去隊(duì)尾排隊(duì)。值得注意的是腔呜,在 AQS 的實(shí)現(xiàn)中叁温,一旦線程進(jìn)入排隊(duì)隊(duì)列,即使是非公平鎖核畴,線程也得乖乖排隊(duì)膝但。

可重入鎖和不可重入鎖

如果一個(gè)線程已經(jīng)獲取到了一個(gè)鎖,那么它可以訪問被這個(gè)鎖鎖住的所有代碼塊谤草。不可重入鎖與之相反跟束。

Synchronized 關(guān)鍵字

Synchronized 是一種獨(dú)占鎖莺奸。在修飾靜態(tài)方法時(shí),鎖的是類對(duì)象冀宴,如 Object.class灭贷。修飾非靜態(tài)方法時(shí),鎖的是對(duì)象略贮,即 this甚疟。修飾方法塊時(shí),鎖的是括號(hào)里的對(duì)象逃延。

每個(gè)對(duì)象有一個(gè)鎖和一個(gè)等待隊(duì)列览妖,鎖只能被一個(gè)線程持有,其他需要鎖的線程需要阻塞等待揽祥。鎖被釋放后讽膏,對(duì)象會(huì)從隊(duì)列中取出一個(gè)并喚醒,喚醒哪個(gè)線程是不確定的拄丰,不保證公平性府树。

類鎖與對(duì)象鎖

synchronized 修飾靜態(tài)方法時(shí),鎖的是類對(duì)象,如 Object.class愈案。修飾非靜態(tài)方法時(shí)挺尾,鎖的是對(duì)象,即 this站绪。

多個(gè)線程是可以同時(shí)執(zhí)行同一個(gè)synchronized實(shí)例方法的,只要它們?cè)L問的對(duì)象是不同的丽柿。

synchronized 鎖住的是對(duì)象而非代碼恢准,只要訪問的是同一個(gè)對(duì)象的 synchronized 方法,即使是不同的代碼甫题,也會(huì)被同步順序訪問馁筐。

此外,需要說明的坠非,synchronized方法不能防止非synchronized方法被同時(shí)執(zhí)行敏沉,所以,一般在保護(hù)變量時(shí)炎码,需要在所有訪問該變量的方法上加上synchronized盟迟。

實(shí)現(xiàn)原理

synchronized 是基于 Java 對(duì)象頭和 Monitor 機(jī)制來實(shí)現(xiàn)的。

Java 對(duì)象頭

一個(gè)對(duì)象在內(nèi)存中包含三部分:對(duì)象頭潦闲,實(shí)例數(shù)據(jù)和對(duì)齊填充攒菠。其中 Java 對(duì)象頭包含兩部分:

Class Metadata Address (類型指針)。存儲(chǔ)類的元數(shù)據(jù)的指針歉闰。虛擬機(jī)通過這個(gè)指針找到它是哪個(gè)類的實(shí)例辖众。

Mark Word(標(biāo)記字段)卓起。存出一些對(duì)象自身運(yùn)行時(shí)的數(shù)據(jù)。包括哈希碼凹炸,GC 分代年齡戏阅,鎖狀態(tài)標(biāo)志等。

Monitor

Mark Word 有一個(gè)字段指向 monitor 對(duì)象啤它。monitor 中記錄了鎖的持有線程奕筐,等待的線程隊(duì)列等信息。前面說的每個(gè)對(duì)象都有一個(gè)鎖和一個(gè)等待隊(duì)列蚕键,就是在這里實(shí)現(xiàn)的救欧。

monitor 對(duì)象由 C++ 實(shí)現(xiàn)。其中有三個(gè)關(guān)鍵字段:

_owner 記錄當(dāng)前持有鎖的線程

_EntryList 是一個(gè)隊(duì)列锣光,記錄所有阻塞等待鎖的線程

_WaitSet 也是一個(gè)隊(duì)列笆怠,記錄調(diào)用 wait() 方法并還未被通知的線程。

Monitor的操作機(jī)制如下:

多個(gè)線程競(jìng)爭(zhēng)鎖時(shí)誊爹,會(huì)先進(jìn)入 EntryList 隊(duì)列蹬刷。競(jìng)爭(zhēng)成功的線程被標(biāo)記為 Owner。其他線程繼續(xù)在此隊(duì)列中阻塞等待频丘。

如果 Owner 線程調(diào)用 wait() 方法办成,則其釋放對(duì)象鎖并進(jìn)入 WaitSet 中等待被喚醒。Owner 被置空搂漠,EntryList 中的線程再次競(jìng)爭(zhēng)鎖迂卢。

如果 Owner 線程執(zhí)行完了,便會(huì)釋放鎖桐汤,Owner 被置空而克,EntryList 中的線程再次競(jìng)爭(zhēng)鎖。

JVM 對(duì) synchronized 的處理

上面了解了 monitor 的機(jī)制怔毛,那虛擬機(jī)是如何將 synchronized 和 monitor 關(guān)聯(lián)起來的呢员萍?分兩種情況:

如果同步的是代碼塊,編譯時(shí)會(huì)直接在同步代碼塊前加上 monitorenter 指令拣度,代碼塊后加上 monitorexit 指令碎绎。這稱為顯示同步。

如果同步的是方法抗果,虛擬機(jī)會(huì)為方法設(shè)置 ACC_SYNCHRONIZED 標(biāo)志筋帖。調(diào)用的時(shí)候 JVM 根據(jù)這個(gè)標(biāo)志判斷是否是同步方法。

JVM 對(duì) synchronized 的優(yōu)化

synchronized 是重量級(jí)鎖窖张,由于消耗太大幕随,虛擬機(jī)對(duì)其做了一些優(yōu)化。

自旋鎖與自適應(yīng)自旋

在許多應(yīng)用中宿接,鎖定狀態(tài)只會(huì)持續(xù)很短的時(shí)間赘淮,為了這么一點(diǎn)時(shí)間去掛起恢復(fù)線程辕录,不值得。我們可以讓等待線程執(zhí)行一定次數(shù)的循環(huán)梢卸,在循環(huán)中去獲取鎖走诞。這項(xiàng)技術(shù)稱為自旋鎖,它可以節(jié)省系統(tǒng)切換線程的消耗蛤高,但仍然要占用處理器蚣旱。在 JDK1.4.2 中,自選的次數(shù)可以通過參數(shù)來控制戴陡。 JDK 1.6又引入了自適應(yīng)的自旋鎖塞绿,不再通過次數(shù)來限制,而是由前一次在同一個(gè)鎖上的自旋時(shí)間及鎖的擁有者的狀態(tài)來決定恤批。

鎖消除

虛擬機(jī)在運(yùn)行時(shí)异吻,如果發(fā)現(xiàn)一段被鎖住的代碼中不可能存在共享數(shù)據(jù),就會(huì)將這個(gè)鎖清除喜庞。

鎖粗化

當(dāng)虛擬機(jī)檢測(cè)到有一串零碎的操作都對(duì)同一個(gè)對(duì)象加鎖時(shí)诀浪,會(huì)把鎖擴(kuò)展到整個(gè)操作序列外部。如 StringBuffer 的 append 操作延都。

輕量級(jí)鎖

對(duì)絕大部分的鎖來說雷猪,在整個(gè)同步周期內(nèi)都不存在競(jìng)爭(zhēng)。如果沒有競(jìng)爭(zhēng)晰房,輕量級(jí)鎖可以使用 CAS 操作避免使用互斥量的開銷求摇。

偏向鎖

偏向鎖的核心思想是,如果一個(gè)線程獲得了鎖殊者,那么鎖就進(jìn)入偏向模式月帝,當(dāng)這個(gè)線程再次請(qǐng)求鎖時(shí),無需再做任何同步操作幽污,即可獲取鎖。

CAS

操作模型

CAS 是 compare and swap 的簡(jiǎn)寫簿姨,即比較并交換距误。它是指一種操作機(jī)制,而不是某個(gè)具體的類或方法扁位。在 Java 平臺(tái)上對(duì)這種操作進(jìn)行了包裝准潭。在 Unsafe 類中,調(diào)用代碼如下:

unsafe.compareAndSwapInt(this, valueOffset, expect, update);

它需要三個(gè)參數(shù)域仇,分別是內(nèi)存位置 V刑然,舊的預(yù)期值 A 和新的值 B。操作時(shí)暇务,先從內(nèi)存位置讀取到值泼掠,然后和預(yù)期值A(chǔ)比較怔软。如果相等,則將此內(nèi)存位置的值改為新值 B择镇,返回 true挡逼。如果不相等,說明和其他線程沖突了腻豌,則不做任何改變家坎,返回 false。

這種機(jī)制在不阻塞其他線程的情況下避免了并發(fā)沖突吝梅,比獨(dú)占鎖的性能高很多虱疏。 CAS 在 Java 的原子類和并發(fā)包中有大量使用。

重試機(jī)制(循環(huán) CAS)

有很多文章說苏携,CAS 操作失敗后會(huì)一直重試直到成功做瞪,這種說法很不嚴(yán)謹(jǐn)。

第一兜叨,CAS 本身并未實(shí)現(xiàn)失敗后的處理機(jī)制穿扳,它只負(fù)責(zé)返回成功或失敗的布爾值,后續(xù)由調(diào)用者自行處理国旷。只不過我們最常用的處理方式是重試而已矛物。

第二,這句話很容易理解錯(cuò)跪但,被理解成重新比較并交換履羞。實(shí)際上失敗的時(shí)候,原值已經(jīng)被修改屡久,如果不更改期望值忆首,再怎么比較都會(huì)失敗。而新值同樣需要修改被环。

所以正確的方法是糙及,使用一個(gè)死循環(huán)進(jìn)行 CAS 操作,成功了就結(jié)束循環(huán)返回筛欢,失敗了就重新從內(nèi)存讀取值和計(jì)算新值浸锨,再調(diào)用 CAS“婀茫看下 AtomicInteger 的源碼就什么都懂了:

public final intincrementAndGet() {for(;;) {? ? ? ? int current = get();? ? ? ? int next = current + 1;if(compareAndSet(current, next))returnnext;? ? }}

底層實(shí)現(xiàn)

CAS 主要分三步柱搜,讀取-比較-修改。其中比較是在檢測(cè)是否有沖突剥险,如果檢測(cè)到?jīng)]有沖突后聪蘸,其他線程還能修改這個(gè)值,那么 CAS 還是無法保證正確性。所以最關(guān)鍵的是要保證比較-修改這兩步操作的原子性健爬。

CAS 底層是靠調(diào)用 CPU 指令集的 cmpxchg 完成的控乾,它是 x86 和 Intel 架構(gòu)中的 compare and exchange 指令。在多核的情況下浑劳,這個(gè)指令也不能保證原子性阱持,需要在前面加上? lock 指令。lock 指令可以保證一個(gè) CPU 核心在操作期間獨(dú)占一片內(nèi)存區(qū)域魔熏。那么 這又是如何實(shí)現(xiàn)的呢衷咽?

在處理器中,一般有兩種方式來實(shí)現(xiàn)上述效果:總線鎖和緩存鎖蒜绽。在多核處理器的結(jié)構(gòu)中镶骗,CPU 核心并不能直接訪問內(nèi)存,而是統(tǒng)一通過一條總線訪問躲雅《︽ⅲ總線鎖就是鎖住這條總線,使其他核心無法訪問內(nèi)存相赁。這種方式代價(jià)太大了相寇,會(huì)導(dǎo)致其他核心停止工作。而緩存鎖并不鎖定總線钮科,只是鎖定某部分內(nèi)存區(qū)域唤衫。當(dāng)一個(gè) CPU 核心將內(nèi)存區(qū)域的數(shù)據(jù)讀取到自己的緩存區(qū)后,它會(huì)鎖定緩存對(duì)應(yīng)的內(nèi)存區(qū)域绵脯。鎖住期間佳励,其他核心無法操作這塊內(nèi)存區(qū)域。

CAS 就是通過這種方式實(shí)現(xiàn)比較和交換操作的原子性的蛆挫。值得注意的是赃承, CAS 只是保證了操作的原子性,并不保證變量的可見性悴侵,因此變量需要加上 volatile 關(guān)鍵字瞧剖。

ABA 問題

上面提到,CAS 保證了比較和交換的原子性可免。但是從讀取到開始比較這段期間筒繁,其他核心仍然是可以修改這個(gè)值的。如果核心將 A 修改為 B巴元,CAS 可以判斷出來。但是如果核心將 A 修改為 B 再修改回 A驮宴。那么 CAS 會(huì)認(rèn)為這個(gè)值并沒有被改變逮刨,從而繼續(xù)操作。這是和實(shí)際情況不符的。解決方案是加一個(gè)版本號(hào)修己。

可重入鎖 ReentrantLock

ReentrantLock 使用代碼實(shí)現(xiàn)了和 synchronized 一樣的語義恢总,包括可重入,保證內(nèi)存可見性和解決競(jìng)態(tài)條件問題等睬愤。相比 synchronized片仿,它還有如下好處:

支持以非阻塞方式獲取鎖

可以響應(yīng)中斷

可以限時(shí)

支持了公平鎖和非公平鎖

基本用法如下:

public class Counter {? ? private final Lock lock = new ReentrantLock();? ? private volatile int count;? ? public voidincr() {? ? ? ? lock.lock();? ? ? ? try {? ? ? ? ? ? count++;? ? ? ? } finally {? ? ? ? ? ? lock.unlock();? ? ? ? }? ? }? ? public intgetCount() {returncount;? ? }}復(fù)制代碼

ReentrantLock 內(nèi)部有兩個(gè)內(nèi)部類,分別是 FairSync 和 NoFairSync尤辱,對(duì)應(yīng)公平鎖和非公平鎖砂豌。他們都繼承自 Sync。Sync 又繼承自AQS光督。

AQS

AQS 全稱 AbstractQueuedSynchronizer阳距。AQS 中有兩個(gè)重要的成員:

成員變量 state。用于表示鎖現(xiàn)在的狀態(tài)结借,用 volatile 修飾筐摘,保證內(nèi)存一致性。同時(shí)所用對(duì) state 的操作都是使用 CAS 進(jìn)行的船老。state 為0表示沒有任何線程持有這個(gè)鎖咖熟,線程持有該鎖后將 state 加1,釋放時(shí)減1柳畔。多次持有釋放則多次加減馍管。

還有一個(gè)雙向鏈表,鏈表除了頭結(jié)點(diǎn)外荸镊,每一個(gè)節(jié)點(diǎn)都記錄了線程的信息咽斧,代表一個(gè)等待線程。這是一個(gè) FIFO 的鏈表躬存。

下面以 ReentrantLock 非公平鎖的代碼看看 AQS 的原理张惹。

請(qǐng)求鎖

請(qǐng)求鎖時(shí)有三種可能:

如果沒有線程持有鎖,則請(qǐng)求成功岭洲,當(dāng)前線程直接獲取到鎖宛逗。

如果當(dāng)前線程已經(jīng)持有鎖,則使用 CAS 將 state 值加1盾剩,表示自己再次申請(qǐng)了鎖雷激,釋放鎖時(shí)減1。這就是可重入性的實(shí)現(xiàn)告私。

如果由其他線程持有鎖屎暇,那么將自己添加進(jìn)等待隊(duì)列。

final voidlock() {if(compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread()); //沒有線程持有鎖時(shí)驻粟,直接獲取鎖根悼,對(duì)應(yīng)情況1elseacquire(1);}public final void acquire(int arg) {if(!tryAcquire(arg) && //在此方法中會(huì)判斷當(dāng)前持有線程是否等于自己,對(duì)應(yīng)情況2? ? ? ? acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) //將自己加入隊(duì)列中,對(duì)應(yīng)情況3? ? ? ? selfInterrupt();}

創(chuàng)建 Node 節(jié)點(diǎn)并加入鏈表

如果沒競(jìng)爭(zhēng)到鎖挤巡,這時(shí)候就要進(jìn)入等待隊(duì)列剩彬。隊(duì)列是默認(rèn)有一個(gè) head 節(jié)點(diǎn)的,并且不包含線程信息矿卑。上面情況3中喉恋,addWaiter 會(huì)創(chuàng)建一個(gè) Node,并添加到鏈表的末尾母廷,Node 中持有當(dāng)前線程的引用轻黑。同時(shí)還有一個(gè)成員變量 waitStatus,表示線程的等待狀態(tài)徘意,初始值為0苔悦。我們還需要關(guān)注兩個(gè)值:

CANCELLED,值為1椎咧,表示取消狀態(tài)玖详,就是說我不要這個(gè)鎖了,請(qǐng)你把我移出去勤讽。

SINGAL蟋座,值為-1,表示下一個(gè)節(jié)點(diǎn)正在掛起等待脚牍,注意是下一個(gè)節(jié)點(diǎn)向臀,不是當(dāng)前節(jié)點(diǎn)。

同時(shí)诸狭,加到鏈表末尾的操作使用了 CAS+死循環(huán)的模式券膀,很有代表性,拿出來看一看:

Node node = new Node(mode);for(;;) {? ? Node oldTail = tail;if(oldTail != null) {? ? ? ? U.putObject(node, Node.PREV, oldTail);if(compareAndSetTail(oldTail, node)) {? ? ? ? ? ? oldTail.next = node;returnnode;? ? ? ? }? ? }else{? ? ? ? initializeSyncQueue();? ? }}

可以看到驯遇,在死循環(huán)里調(diào)用了 CAS 的方法芹彬。如果多個(gè)線程同時(shí)調(diào)用該方法,那么每次循環(huán)都只有一個(gè)線程執(zhí)行成功叉庐,其他線程進(jìn)入下一次循環(huán)舒帮,重新調(diào)用。N個(gè)線程就會(huì)循環(huán)N次陡叠。這樣就在無鎖的模式下實(shí)現(xiàn)了并發(fā)模型玩郊。

掛起等待

如果此節(jié)點(diǎn)的上一個(gè)節(jié)點(diǎn)是頭部節(jié)點(diǎn),則再次嘗試獲取鎖枉阵,獲取到了就移除并返回译红。獲取不到就進(jìn)入下一步;

判斷前一個(gè)節(jié)點(diǎn)的 waitStatus兴溜,如果是 SINGAL临庇,則返回 true反璃,并調(diào)用 LockSupport.park() 將線程掛起;

如果是 CANCELLED假夺,則將前一個(gè)節(jié)點(diǎn)移除;

如果是其他值斋攀,則將前一個(gè)節(jié)點(diǎn)的 waitStatus 標(biāo)記為 SINGAL已卷,進(jìn)入下一次循環(huán)。

可以看到淳蔼,一個(gè)線程最多有兩次機(jī)會(huì)侧蘸,還競(jìng)爭(zhēng)不到就去掛起等待。

final boolean acquireQueued(final Node node, int arg) {? ? try {? ? ? ? boolean interrupted =false;for(;;) {? ? ? ? ? ? final Node p = node.predecessor();if(p == head && tryAcquire(arg)) {setHead(node);? ? ? ? ? ? ? ? p.next = null; //helpGCreturninterrupted;? ? ? ? ? ? }if(shouldParkAfterFailedAcquire(p, node) &&? ? ? ? ? ? ? ? parkAndCheckInterrupt())? ? ? ? ? ? ? ? interrupted =true;? ? ? ? }? ? } catch (Throwable t) {? ? ? ? cancelAcquire(node);? ? ? ? throw t;? ? }}

釋放鎖

調(diào)用 tryRelease鹉梨,此方法由子類實(shí)現(xiàn)讳癌。實(shí)現(xiàn)非常簡(jiǎn)單,如果當(dāng)前線程是持有鎖的線程存皂,就將 state 減1晌坤。減完后如果 state 大于0,表示當(dāng)前線程仍然持有鎖旦袋,返回 false骤菠。如果等于0,表示已經(jīng)沒有線程持有鎖疤孕,返回 true商乎,進(jìn)入下一步;

如果頭部節(jié)點(diǎn)的 waitStatus 不等于0祭阀,則調(diào)用LockSupport.unpark()喚醒其下一個(gè)節(jié)點(diǎn)鹉戚。頭部節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)就是等待隊(duì)列中的第一個(gè)線程,這反映了 AQS 先進(jìn)先出的特點(diǎn)专控。另外抹凳,即使是非公平鎖,進(jìn)入隊(duì)列之后踩官,還是得按順序來却桶。

public final boolean release(int arg) {if(tryRelease(arg)) { //將 state 減1? ? ? ? Node h = head;if(h != null && h.waitStatus != 0)? ? ? ? ? ? unparkSuccessor(h);returntrue;? ? }returnfalse;}private void unparkSuccessor(Node node) {? ? int ws = node.waitStatus;if(ws < 0)? ? ? ? node.compareAndSetWaitStatus(ws, 0);? ? ? ? ? ? Node s = node.next;if(s == null || s.waitStatus > 0) {? ? ? ? s = null;for(Node p = tail; p != node && p != null; p = p.prev)if(p.waitStatus <= 0)? ? ? ? ? ? ? ? s = p;? ? }if(s != null) //喚醒第一個(gè)等待的線程? ? ? ? LockSupport.unpark(s.thread);}

公平鎖如何實(shí)現(xiàn)

上面分析的是非公平鎖,那公平鎖呢蔗牡?很簡(jiǎn)單颖系,在競(jìng)爭(zhēng)鎖之前判斷一下等待隊(duì)列中有沒有線程在等待就行了。

protected final boolean tryAcquire(int acquires) {? ? final Thread current = Thread.currentThread();? ? int c = getState();if(c == 0) {if(!hasQueuedPredecessors() && //判斷等待隊(duì)列是否有節(jié)點(diǎn)? ? ? ? ? ? compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);returntrue;? ? ? ? }? ? }? ? ......returnfalse;}

可重入讀寫鎖 ReentrantReadWriteLock

讀寫鎖機(jī)制

理解 ReentrantLock 和 AQS 之后辩越,再來理解讀寫鎖就很簡(jiǎn)單了嘁扼。讀寫鎖有一個(gè)讀鎖和一個(gè)寫鎖,分別對(duì)應(yīng)讀操作和鎖操作黔攒。鎖的特性如下:

只有一個(gè)線程可以獲取到寫鎖趁啸。在獲取寫鎖時(shí)强缘,只有沒有任何線程持有任何鎖才能獲取成功;

如果有線程正持有寫鎖不傅,其他任何線程都獲取不到任何鎖旅掂;

沒有線程持有寫鎖時(shí),可以有多個(gè)線程獲取到讀鎖访娶。

上面鎖的特點(diǎn)保證了可以并發(fā)讀取商虐,這大大提高了效率,在實(shí)際開發(fā)中非常有用崖疤。那么在具體是如何實(shí)現(xiàn)的呢秘车?

實(shí)現(xiàn)原理

讀寫鎖雖然有兩個(gè)鎖,但實(shí)際上只有一個(gè)等待隊(duì)列劫哼。

獲取寫鎖時(shí)叮趴,要保證沒有任何線程持有鎖;

寫鎖釋放后权烧,會(huì)喚醒隊(duì)列第一個(gè)線程眯亦,可能是讀鎖和寫鎖;

獲取讀鎖時(shí)豪嚎,先判斷寫鎖有沒有被持有搔驼,沒有就可以獲取成功;

獲取讀鎖成功后侈询,會(huì)將隊(duì)列中等待讀鎖的線程挨個(gè)喚醒舌涨,知道遇到等待寫鎖的線程位置;

釋放讀鎖時(shí)扔字,要檢查讀鎖數(shù)囊嘉,如果為0,則喚醒隊(duì)列中的下一個(gè)線程革为,否則不進(jìn)行操作扭粱。

在此我向大家推薦一個(gè)架構(gòu)學(xué)習(xí)交流群。交流學(xué)習(xí)群號(hào):938837867 暗號(hào):555 里面會(huì)分享一些資深架構(gòu)師錄制的視頻錄像:有Spring震檩,MyBatis琢蛤,Netty源碼分析,高并發(fā)抛虏、高性能博其、分布式、微服務(wù)架構(gòu)的原理迂猴,JVM性能優(yōu)化慕淡、分布式架構(gòu)等這些成為架構(gòu)師必備

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市沸毁,隨后出現(xiàn)的幾起案子峰髓,更是在濱河造成了極大的恐慌傻寂,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件携兵,死亡現(xiàn)場(chǎng)離奇詭異疾掰,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)徐紧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門个绍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人浪汪,你說我怎么就攤上這事×菟洌” “怎么了死遭?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)凯旋。 經(jīng)常有香客問我呀潭,道長(zhǎng),這世上最難降的妖魔是什么至非? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任钠署,我火速辦了婚禮,結(jié)果婚禮上荒椭,老公的妹妹穿的比我還像新娘谐鼎。我一直安慰自己,他們只是感情好趣惠,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布狸棍。 她就那樣靜靜地躺著,像睡著了一般味悄。 火紅的嫁衣襯著肌膚如雪草戈。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天侍瑟,我揣著相機(jī)與錄音唐片,去河邊找鬼。 笑死涨颜,一個(gè)胖子當(dāng)著我的面吹牛费韭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播咐低,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼揽思,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了见擦?” 一聲冷哼從身側(cè)響起钉汗,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤羹令,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后损痰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體福侈,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年卢未,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了肪凛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡辽社,死狀恐怖伟墙,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情滴铅,我是刑警寧澤戳葵,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站汉匙,受9級(jí)特大地震影響拱烁,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜噩翠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一戏自、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧伤锚,春花似錦擅笔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至玄呛,卻和暖如春阅懦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背徘铝。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工耳胎, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人惕它。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓怕午,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親淹魄。 傳聞我的和親對(duì)象是個(gè)殘疾皇子郁惜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

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