淺談一下Java中的幾種JVM級別的鎖
前言
根據(jù)摩爾定律,計算機的性能將繼續(xù)飆升,因為計算基礎(chǔ)設(shè)施的相關(guān)成本將隨著時間的推移繼續(xù)下降。
具體到CPU,已經(jīng)從簡單的單核系統(tǒng)發(fā)展到多核系統(tǒng),緩存性能也有了飛躍性的提升。隨著多核 CPU 的出現(xiàn),計算機現(xiàn)在可以同時運行多個任務(wù)。
并且,隨著硬件開發(fā)的多項提升帶來的顯著效率提升,軟件層面的多線程編程已經(jīng)成為必然趨勢。
然而,多線程編程也帶來了一些數(shù)據(jù)安全問題。
隨著所有這些趨勢的發(fā)展,業(yè)界已經(jīng)認識到,當(dāng)存在安全漏洞時,也必須有相應(yīng)的防護措施。
順應(yīng)這種趨勢,虛擬“鎖”被發(fā)明出來,以解決線程的安全問題。
在這篇文章中,我們將研究多年來出現(xiàn)的 Java 中幾種典型的 JVM 級鎖。
1、synchronized
synchronized 關(guān)鍵字是 Java 中經(jīng)典且非常典型的鎖。
事實上,它也是最常用的一種。在 JDK 1.6 之前,“synchronized”是一個相當(dāng)“重量級”的鎖。
不過隨著Java的更新升級,這個鎖也在不斷的優(yōu)化。
如今,這把鎖變得不那么“沉重”了。而且,事實上,在某些場景下,它的性能甚至優(yōu)于典型的輕量級鎖。
而且在帶有synchronized關(guān)鍵字的方法和代碼塊中,同一時刻只允許一個線程訪問特定的代碼段,防止多個線程并發(fā)修改同一塊數(shù)據(jù)。
1.1)鎖升級
在JDK 1.5(含)之前,synchronized關(guān)鍵字的底層實現(xiàn)比較重,因此被稱為“重量級鎖”。
不過在JDK 1.5之后,對“synchronized”鎖進行了各種改進,變得不那么重量級了。
因此,實現(xiàn)方法就是鎖升級過程。我們先看看JDK 1.5之后“同步”鎖是怎么實現(xiàn)的。
說到同步鎖的原理,首先要了解Java對象在內(nèi)存中的布局。

如上圖所示,創(chuàng)建一個對象后,該對象在JVM HotSpot虛擬機的Java內(nèi)存中的存儲布局可以分為三種。
1)對象頭
該區(qū)域存儲的信息分為兩部分。
對象本身的運行時數(shù)據(jù)(MarkWord):該數(shù)據(jù)存儲hashCode、垃圾收集(GC)世代年齡、鎖類型標志、偏向鎖線程的ID、指向LockRecord的Compare and Swap(CAS)鎖指針的線程,除了其他信息。 synchronized鎖的機制與這部分(MarkWord)高度相關(guān)。 MarkWord 的最低三位表示鎖定狀態(tài)。對于這三個位,其中一個是偏向鎖定位,另外兩個是普通鎖定位。對象的類指針(Class Pointer):對象的指針是指向其類元數(shù)據(jù)的指針。 JVM 使用它來確定實例的類。
2)實例數(shù)據(jù)區(qū)
該區(qū)域存放對象的有效信息,如對象中所有字段的內(nèi)容。
3)對齊填充
JVM HotSpot的實現(xiàn)規(guī)定對象的起始地址必須是8字節(jié)的整數(shù)倍。也就是說,64位操作系統(tǒng)一次讀取的數(shù)據(jù)是64位的整數(shù)倍,即8個字節(jié)。因此,HotSpot 進行“對齊”以高效地讀取對象。如果對象的實際內(nèi)存大小不是 8 字節(jié)的整數(shù)倍,HotSpot 會將對象“填充”為 8 字節(jié)的整數(shù)倍。因此,對齊和填充區(qū)域的大小是動態(tài)的。
1.2)synchronized鎖升級
當(dāng)線程進入synchronized狀態(tài)并嘗試獲取鎖時,synchronized鎖的升級過程如下。

總結(jié)一下,同步鎖的升級順序是:偏向鎖>輕量級鎖>重量級鎖。
The detailed triggering of lock upgrade in each step is as follows.
1)Biased Lock:(偏向鎖)
在 JDK 1.8 中,默認是輕量級鎖。但是,通過設(shè)置 -XX:BiasedLockingStartupDelay = 0,在同步對象后立即附加偏向鎖。當(dāng)線程處于偏向鎖狀態(tài)時,MarkWord記錄當(dāng)前線程的ID。
2)Upgrading to the Lightweight Lock:(升級到輕量級鎖) 當(dāng)下一個線程競爭偏向鎖時,系統(tǒng)首先檢查MarkWord中存儲的線程ID是否與本線程的ID一致。如果不是,系統(tǒng)立即撤銷偏向鎖,升級為輕量級鎖。每個線程在自己的線程棧中生成一個LockRecord(LR)。然后,每個線程通過CAS操作(自旋)將鎖對象頭中的MarkWord設(shè)置為指向自己LR的指針。如果一個線程成功設(shè)置了MarkWord,那么該線程就獲得了鎖。因此,為“同步”執(zhí)行的 CAS 操作是通過 HotSpot 的 bytecodeInterpreter.cpp 文件中的 C++ 代碼完成的。
3)Upgrading to the Heavyweight Lock:(升級到重量級鎖)
如果鎖競爭加劇,比如線程自旋數(shù)或者自旋線程數(shù)超過了一個閾值,這個閾值是JVM自己控制的,對于1.6以后的JDK版本,鎖升級為重量級鎖。然后,重量級鎖向操作系統(tǒng)申請資源。
同時,該線程也被掛起,進入操作系統(tǒng)內(nèi)核態(tài)的等待隊列,等待操作系統(tǒng)對其進行調(diào)度,映射回用戶態(tài)。在重量級鎖中,需要從內(nèi)核態(tài)轉(zhuǎn)換到用戶態(tài),這個過程需要比較長的時間,這也是它被定性為“重量級”的原因之一。
1.3)其他:
1)可重入性:
synchronized鎖內(nèi)部有一個強制原子性的鎖機制,就是可重入鎖。當(dāng)一個線程使用synchronized方法時,會調(diào)用該對象的另一個synchronized方法。即線程獲得對象鎖后,一旦再次請求該對象鎖,該線程總能獲得該鎖。在Java中,線程獲取對象鎖的操作是基于線程的,而不是基于調(diào)用的。
synchrnoized塊、語句中所對應(yīng)的對象頭中的MarkWord記錄了鎖的線程持有者和計數(shù)器。當(dāng)一個線程請求成功后,JVM記錄持有鎖的線程,并將計數(shù)置為1。此時,如果有另一個線程請求鎖,則該線程必須等待。
當(dāng)持有鎖的線程再次請求鎖時,可以再次獲得鎖,計數(shù)隨之遞增。當(dāng)線程退出同步方法或塊時,計數(shù)會遞減。最后,如果計數(shù)為 0,則釋放鎖。
2)Pessimistic Lock (Mutex and Exclusive Lock):(悲觀鎖(互斥和排他鎖))
synchronized鎖是悲觀鎖,更確切地說是排他鎖。換句話說,如果當(dāng)前線程獲得了鎖,任何其他需要鎖的線程都必須等待。鎖競爭繼續(xù),直到持有鎖的線程釋放鎖。
2、ReentrantLock
ReentrantLock 和synchronized類似,但其實現(xiàn)方式與同步鎖有較大區(qū)別。具體來說,它是基于經(jīng)典的AbstractQueueSyncronized(AQS)實現(xiàn)的。 AQS是基于volatile和CAS實現(xiàn)的。 AQS維護volatile類型的state變量來統(tǒng)計重入鎖的重入嘗試次數(shù)。同樣,加鎖和釋放鎖也是基于這個變量。 ReentrantLock提供了一些synchronized鎖所沒有的額外特性,所以比synchronized鎖要好。

2.1)ReentrantLock特性:
1)可重入:
ReentrantLock 和 synchronized 關(guān)鍵字一樣,都是支持重入的鎖。但是,它們的實現(xiàn)方法略有不同。 ReentrantLock通過AQS的狀態(tài)來判斷資源是否已經(jīng)被鎖定。對于同一個線程,如果被鎖定,狀態(tài)值加1,如果被解鎖,狀態(tài)值減1。注意解鎖只對當(dāng)前獨占線程有效,否則會出現(xiàn)異常。如果狀態(tài)值為0,則解鎖成功。
2)手動加鎖、解鎖:
synchronized 關(guān)鍵字自動鎖定和解鎖。相比之下,ReentrantLock 需要 lock() 和 unlock() 方法以及 try/finally 語句塊來手動鎖定和解鎖。
3)lock timeout:
synchronized 關(guān)鍵字不能設(shè)置鎖定超時時間。如果在獲取鎖的線程中發(fā)生死鎖,其他線程將保持阻塞狀態(tài)。 ReentrantLock 提供了 tryLock 方法,可以為獲得鎖的線程設(shè)置超時時間。如果超過超時時間,則跳過該線程,不執(zhí)行任何操作,從而防止死鎖。
4)Fair and Unfair Locks
synchronized 關(guān)鍵字是一種非公平鎖,第一個搶到鎖的線程先運行。通過在ReentrantLock的構(gòu)造方法中設(shè)置true或false,可以實現(xiàn)公平鎖和非公平鎖。如果設(shè)置為true,線程需要遵循“先到先得”的規(guī)則。每次線程要獲取鎖時,都會構(gòu)造一個線程節(jié)點,然后追加到雙向鏈表的“尾部”進行排隊,等待隊列中的前一個節(jié)點釋放鎖資源。
5)可中斷:
ReentrantLock 中的 lockInterruptibly() 方法允許線程在被阻塞時響應(yīng)中斷。比如線程t1通過lockInterruptibly()方法獲得了一個ReentrantLock,運行了一個long task。其他線程可以使用interrupt()方法立即中斷線程t1的運行,獲得線程t1的ReentrantLock。但是,ReentrantLock的lock()方法或者有synchronized鎖的線程是不會響應(yīng)其他線程的interrupt()方法的,直到這個方法主動釋放鎖。
2.2)ReentrantReadWriteLock
ReentrantReadWriteLock(讀寫鎖)其實就是兩把鎖,一把是WriteLock(寫鎖),一把是ReadLock(讀鎖)。讀寫鎖的規(guī)則是:read-read non-exclusive, read-write exclusive, and write-write exclusive。在一些實際場景中,讀的頻率遠高于寫的頻率。如果使用普通鎖進行并發(fā)控制,讀寫互斥,效率低下。
為了優(yōu)化這種場景下的運行效率,讀寫鎖應(yīng)運而生??偟膩碚f,獨占鎖的低效率來源于高并發(fā)下臨界區(qū)的激烈競爭,導(dǎo)致線程上下文切換。當(dāng)并發(fā)不是很高的時候,讀寫鎖的效率可能會低于排他鎖,因為需要額外維護。因此,您需要根據(jù)實際情況選擇合適的鎖具。
ReentrantReadWriteLock也是基于AQS實現(xiàn)的。 ReentrantLock 和 ReentrantReadWriteLock 的區(qū)別在于后者具有共享鎖和排它鎖的屬性。讀寫鎖中的加鎖和解鎖是基于Sync的,繼承自AQS。主要由AQS中的state和node中的waitState變量實現(xiàn)。
讀寫鎖的實現(xiàn)與普通互斥鎖的主要區(qū)別在于需要分別記錄讀鎖狀態(tài)和寫鎖狀態(tài),等待隊列需要區(qū)別對待這兩種鎖操作。在ReentrantReadWriteLock中,AQS中的int型狀態(tài)分為高16位和低16位分別記錄讀鎖和寫鎖狀態(tài),如下圖。

1)The WriteLock (Write Lock) Is a Pessimistic Lock (Exclusive Lock or Mutex)
通過計算state&((1<<16)-1),將state的高16位全部擦除。因此,狀態(tài)的低位記錄了寫鎖的重入次數(shù)。
下面是獲取寫鎖的源碼。
/**
* 獲取寫鎖
Acquires the write lock.
* 如果此時沒有任何線程持有寫鎖或者讀鎖,那么當(dāng)前線程執(zhí)行CAS操作更新status,
* 若更新成功,則設(shè)置讀鎖重入次數(shù)為1,并立即返回
* <p>Acquires the write lock if neither the read nor write lock
* are held by another thread
* and returns immediately, setting the write lock hold count to
* one.
* 如果當(dāng)前線程已經(jīng)持有該寫鎖,那么將寫鎖持有次數(shù)設(shè)置為1,并立即返回
* <p>If the current thread already holds the write lock then the
* hold count is incremented by one and the method returns
* immediately.
* 如果該鎖已經(jīng)被另外一個線程持有,那么停止該線程的CPU調(diào)度并進入休眠狀態(tài),
* 直到該寫鎖被釋放,且成功將寫鎖持有次數(shù)設(shè)置為1才表示獲取寫鎖成功
* <p>If the lock is held by another thread then the current
* thread becomes disabled for thread scheduling purposes and
* lies dormant until the write lock has been acquired, at which
* time the write lock hold count is set to one.
*/
public void lock() {
sync.acquire(1);
}
/**
* 該方法為以獨占模式獲取鎖,忽略中斷
* 如果調(diào)用一次該"tryAcquire"方法更新status成功,則直接返回,代表搶鎖成功
* 否則,將會進入同步隊列等待,不斷執(zhí)行"tryAcquire"方法嘗試CAS更新status狀態(tài),直到成功搶到鎖
* 其中"tryAcquire"方法在NonfairSync(公平鎖)中和FairSync(非公平鎖)中都有各自的實現(xiàn)
*
* Acquires in exclusive mode, ignoring interrupts. Implemented
* by invoking at least once {@link #tryAcquire},
* returning on success. Otherwise the thread is queued, possibly
* repeatedly blocking and unblocking, invoking {@link
* #tryAcquire} until success. This method can be used
* to implement method {@link Lock#lock}.
*
* @param arg the acquire argument. This value is conveyed to
* {@link #tryAcquire} but is otherwise uninterpreted and
* can represent anything you like.
*/
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
/*
* Walkthrough:
* 1、如果讀寫鎖的計數(shù)不為0,且持有鎖的線程不是當(dāng)前線程,則返回false
* 1. If read count nonzero or write count nonzero
* and owner is a different thread, fail.
* 2、如果持有鎖的計數(shù)不為0且計數(shù)總數(shù)超過限定的最大值,也返回false
* 2. If count would saturate, fail. (This can only
* happen if count is already nonzero.)
* 3、如果該鎖是可重入或該線程在隊列中的策略是允許它嘗試搶鎖,那么該線程就能獲取鎖
* 3. Otherwise, this thread is eligible for lock if
* it is either a reentrant acquire or
* queue policy allows it. If so, update state
* and set owner.
*/
Thread current = Thread.currentThread();
//獲取讀寫鎖的狀態(tài)
int c = getState();
//獲取該寫鎖重入的次數(shù)
int w = exclusiveCount(c);
//如果讀寫鎖狀態(tài)不為0,說明已經(jīng)有其他線程獲取了讀鎖或?qū)戞i
if (c != 0) {
//如果寫鎖重入次數(shù)為0,說明有線程獲取到讀鎖,根據(jù)"讀寫鎖互斥"原則,返回false
//或者如果寫鎖重入次數(shù)不為0,且獲取寫鎖的線程不是當(dāng)前線程,根據(jù)"寫鎖獨占"原則,返回false
// (Note: if c != 0 and w == 0 then shared count != 0)
if (w == 0 || current != getExclusiveOwnerThread())
return false;
//如果寫鎖可重入次數(shù)超過最大次數(shù)(65535),則拋異常
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
//到這里說明該線程是重入寫鎖,更新重入寫鎖的計數(shù)(+1),返回true
// Reentrant acquire
setState(c + acquires);
return true;
}
//如果讀寫鎖狀態(tài)為0,說明讀鎖和寫鎖都沒有被獲取,會走下面兩個分支:
//如果要阻塞或者執(zhí)行CAS操作更新讀寫鎖的狀態(tài)失敗,則返回false
//如果不需要阻塞且CAS操作成功,則當(dāng)前線程成功拿到鎖,設(shè)置鎖的owner為當(dāng)前線程,返回true
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}Source code for releasing the write lock:
/*
* Note that tryRelease and tryAcquire can be called by
* Conditions. So it is possible that their arguments contain
* both read and write holds that are all released during a
* condition wait and re-established in tryAcquire.
*/
protected final boolean tryRelease(int releases) {
//若鎖的持有者不是當(dāng)前線程,拋出異常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//寫鎖的可重入計數(shù)減掉releases個
int nextc = getState() - releases;
//如果寫鎖重入計數(shù)為0了,則說明寫鎖被釋放了
boolean free = exclusiveCount(nextc) == 0;
if (free)
//若寫鎖被釋放,則將鎖的持有者設(shè)置為null,進行GC
setExclusiveOwnerThread(null);
//更新寫鎖的重入計數(shù)
setState(nextc);
return free;
}2)The ReadLock (Read Lock) Is a Shared Lock (Optimistic Lock)
通過計算無符號補零的狀態(tài)>>>16,引入了 16 個附加位。所以state的高位記錄了寫鎖的重入次數(shù)。
獲取讀鎖的過程比獲取寫鎖稍微復(fù)雜一些。首先系統(tǒng)判斷寫鎖的計數(shù)是否為0,當(dāng)前線程沒有持有排他鎖。如果是,系統(tǒng)直接返回結(jié)果。如果不是,系統(tǒng)檢查讀線程是否需要阻塞,讀鎖數(shù)量是否小于閾值,設(shè)置狀態(tài)比較是否成功。
如果當(dāng)前不存在讀取鎖,則設(shè)置第一個讀取線程的 firstReader 和 firstReaderHoldCount。如果當(dāng)前線程是第一個讀取線程,則 firstReaderHoldCount 值遞增。否則,設(shè)置當(dāng)前線程對應(yīng)的HoldCounter對象的值。更新成功后,當(dāng)前線程的重入計數(shù)記錄在當(dāng)前線程副本中的firstReaderHoldCount的readHolds(ThreadLocal類型)中。這是為了實現(xiàn)JDK 1.6中新增的getReadHoldCount()方法。該方法可以獲得當(dāng)前線程重新進入共享鎖的次數(shù)。即狀態(tài)中記錄了多個線程的總重入次數(shù)。
引入這個方法讓代碼復(fù)雜了很多,但是原理還是很簡單的:如果只有一個線程,則不需要使用ThreadLocal,直接將重入計數(shù)存儲在firstReaderHoldCount成員變量中即可。當(dāng)另一個線程發(fā)生時,需要使用ThreadLocal變量,readHolds。每個線程都有自己的副本,用來保存自己的重入計數(shù)。
下面是獲取讀鎖的源碼:
/**
* 獲取讀鎖
* Acquires the read lock.
* 如果寫鎖未被其他線程持有,執(zhí)行CAS操作更新status值,獲取讀鎖后立即返回
* <p>Acquires the read lock if the write lock is not held by
* another thread and returns immediately.
*
* 如果寫鎖被其他線程持有,那么停止該線程的CPU調(diào)度并進入休眠狀態(tài),直到該讀鎖被釋放
* <p>If the write lock is held by another thread then
* the current thread becomes disabled for thread scheduling
* purposes and lies dormant until the read lock has been acquired.
*/
public void lock() {
sync.acquireShared(1);
}
/**
* 該方法為以共享模式獲取讀鎖,忽略中斷
* 如果調(diào)用一次該"tryAcquireShared"方法更新status成功,則直接返回,代表搶鎖成功
* 否則,將會進入同步隊列等待,不斷執(zhí)行"tryAcquireShared"方法嘗試CAS更新status狀態(tài),直到成功搶到鎖
* 其中"tryAcquireShared"方法在NonfairSync(公平鎖)中和FairSync(非公平鎖)中都有各自的實現(xiàn)
* (看這注釋是不是和寫鎖很對稱)
* Acquires in shared mode, ignoring interrupts. Implemented by
* first invoking at least once {@link #tryAcquireShared},
* returning on success. Otherwise the thread is queued, possibly
* repeatedly blocking and unblocking, invoking {@link
* #tryAcquireShared} until success.
*
* @param arg the acquire argument. This value is conveyed to
* {@link #tryAcquireShared} but is otherwise uninterpreted
* and can represent anything you like.
*/
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
protected final int tryAcquireShared(int unused) {
/*
* Walkthrough:
* 1、如果已經(jīng)有其他線程獲取到了寫鎖,根據(jù)"讀寫互斥"原則,搶鎖失敗,返回-1
* 1.If write lock held by another thread, fail.
* 2、如果該線程本身持有寫鎖,那么看一下是否要readerShouldBlock,如果不需要阻塞,
* 則執(zhí)行CAS操作更新state和重入計數(shù)。
* 這里要注意的是,上面的步驟不檢查是否可重入(因為讀鎖屬于共享鎖,天生支持可重入)
* 2. Otherwise, this thread is eligible for
* lock wrt state, so ask if it should block
* because of queue policy. If not, try
* to grant by CASing state and updating count.
* Note that step does not check for reentrant
* acquires, which is postponed to full version
* to avoid having to check hold count in
* the more typical non-reentrant case.
* 3、如果因為CAS更新status失敗或者重入計數(shù)超過最大值導(dǎo)致步驟2執(zhí)行失敗
* 那就進入到fullTryAcquireShared方法進行死循環(huán),直到搶鎖成功
* 3. If step 2 fails either because thread
* apparently not eligible or CAS fails or count
* saturated, chain to version with full retry loop.
*/
//當(dāng)前嘗試獲取讀鎖的線程
Thread current = Thread.currentThread();
//獲取該讀寫鎖狀態(tài)
int c = getState();
//如果有線程獲取到了寫鎖 ,且獲取寫鎖的不是當(dāng)前線程則返回失敗
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
//獲取讀鎖的重入計數(shù)
int r = sharedCount(c);
//如果讀線程不應(yīng)該被阻塞,且重入計數(shù)小于最大值,且CAS執(zhí)行讀鎖重入計數(shù)+1成功,則執(zhí)行線程重入的計數(shù)加1操作,返回成功
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
//如果還未有線程獲取到讀鎖,則將firstReader設(shè)置為當(dāng)前線程,firstReaderHoldCount設(shè)置為1
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
//如果firstReader是當(dāng)前線程,則將firstReader的重入計數(shù)變量firstReaderHoldCount加1
firstReaderHoldCount++;
} else {
//否則說明有至少兩個線程共享讀鎖,獲取共享鎖重入計數(shù)器HoldCounter
//從HoldCounter中拿到當(dāng)前線程的線程變量cachedHoldCounter,將此線程的重入計數(shù)count加1
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
//如果上面的if條件有一個都不滿足,則進入到這個方法里進行死循環(huán)重新獲取
return fullTryAcquireShared(current);
}
/**
* 用于處理CAS操作state失敗和tryAcquireShared中未執(zhí)行獲取可重入鎖動作的full方法(補償方法?)
* Full version of acquire for reads, that handles CAS misses
* and reentrant reads not dealt with in tryAcquireShared.
*/
final int fullTryAcquireShared(Thread current) {
/*
* 此代碼與tryAcquireShared中的代碼有部分相似的地方,
* 但總體上更簡單,因為不會使tryAcquireShared與重試和延遲讀取保持計數(shù)之間的復(fù)雜判斷
* This code is in part redundant with that in
* tryAcquireShared but is simpler overall by not
* complicating tryAcquireShared with interactions between
* retries and lazily reading hold counts.
*/
HoldCounter rh = null;
//死循環(huán)
for (;;) {
//獲取讀寫鎖狀態(tài)
int c = getState();
//如果有線程獲取到了寫鎖
if (exclusiveCount(c) != 0) {
//如果獲取寫鎖的線程不是當(dāng)前線程,返回失敗
if (getExclusiveOwnerThread() != current)
return -1;
// else we hold the exclusive lock; blocking here
// would cause deadlock.
} else if (readerShouldBlock()) {//如果沒有線程獲取到寫鎖,且讀線程要阻塞
// Make sure we're not acquiring read lock reentrantly
//如果當(dāng)前線程為第一個獲取到讀鎖的線程
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
} else { //如果當(dāng)前線程不是第一個獲取到讀鎖的線程(也就是說至少有有一個線程獲取到了讀鎖)
//
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;
}
}
/**
*下面是既沒有線程獲取寫鎖,當(dāng)前線程又不需要阻塞的情況
*/
//重入次數(shù)等于最大重入次數(shù),拋異常
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
//如果執(zhí)行CAS操作成功將讀寫鎖的重入計數(shù)加1,則對當(dāng)前持有這個共享讀鎖的線程的重入計數(shù)加1,然后返回成功
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;
}
}
}接下來是釋放讀鎖的源碼:
/**
* Releases in shared mode. Implemented by unblocking one or more
* threads if {@link #tryReleaseShared} returns true.
*
* @param arg the release argument. This value is conveyed to
* {@link #tryReleaseShared} but is otherwise uninterpreted
* and can represent anything you like.
* @return the value returned from {@link #tryReleaseShared}
*/
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {//嘗試釋放一次共享鎖計數(shù)
doReleaseShared();//真正釋放鎖
return true;
}
return false;
}
/**
*此方法表示讀鎖線程釋放鎖。
*首先判斷當(dāng)前線程是否為第一個讀線程firstReader,
*若是,則判斷第一個讀線程占有的資源數(shù)firstReaderHoldCount是否為1,
若是,則設(shè)置第一個讀線程firstReader為空,否則,將第一個讀線程占有的資源數(shù)firstReaderHoldCount減1;
若當(dāng)前線程不是第一個讀線程,
那么首先會獲取緩存計數(shù)器(上一個讀鎖線程對應(yīng)的計數(shù)器 ),
若計數(shù)器為空或者tid不等于當(dāng)前線程的tid值,則獲取當(dāng)前線程的計數(shù)器,
如果計數(shù)器的計數(shù)count小于等于1,則移除當(dāng)前線程對應(yīng)的計數(shù)器,
如果計數(shù)器的計數(shù)count小于等于0,則拋出異常,之后再減少計數(shù)即可。
無論何種情況,都會進入死循環(huán),該循環(huán)可以確保成功設(shè)置狀態(tài)state
*/
protected final boolean tryReleaseShared(int unused) {
// 獲取當(dāng)前線程
Thread current = Thread.currentThread();
if (firstReader == current) { // 當(dāng)前線程為第一個讀線程
// assert firstReaderHoldCount > 0;
if (firstReaderHoldCount == 1) // 讀線程占用的資源數(shù)為1
firstReader = null;
else // 減少占用的資源
firstReaderHoldCount--;
} else { // 當(dāng)前線程不為第一個讀線程
// 獲取緩存的計數(shù)器
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current)) // 計數(shù)器為空或者計數(shù)器的tid不為當(dāng)前正在運行的線程的tid
// 獲取當(dāng)前線程對應(yīng)的計數(shù)器
rh = readHolds.get();
// 獲取計數(shù)
int count = rh.count;
if (count <= 1) { // 計數(shù)小于等于1
// 移除
readHolds.remove();
if (count <= 0) // 計數(shù)小于等于0,拋出異常
throw unmatchedUnlockException();
}
// 減少計數(shù)
--rh.count;
}
for (;;) { // 死循環(huán)
// 獲取狀態(tài)
int c = getState();
// 獲取狀態(tài)
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc)) // 比較并進行設(shè)置
// 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;
}
}
/**真正釋放鎖
* Release action for shared mode -- signals successor and ensures
* propagation. (Note: For exclusive mode, release just amounts
* to calling unparkSuccessor of head if it needs signal.)
*/
private void doReleaseShared() {
/*
* Ensure that a release propagates, even if there are other
* in-progress acquires/releases. This proceeds in the usual
* way of trying to unparkSuccessor of head if it needs
* signal. But if it does not, status is set to PROPAGATE to
* ensure that upon release, propagation continues.
* Additionally, we must loop in case a new node is added
* while we are doing this. Also, unlike other uses of
* unparkSuccessor, we need to know if CAS to reset status
* fails, if so rechecking.
*/
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;
}
}通過分析可以看出,當(dāng)一個線程持有讀鎖時,這個線程是無法獲取到寫鎖的,因為無論是否持有讀鎖,如果當(dāng)前讀鎖被占用,它獲取寫鎖的嘗試都會失敗通過當(dāng)前線程。
接下來,當(dāng)一個線程持有寫鎖時,該線程可以繼續(xù)獲取讀鎖。在獲取讀鎖的過程中,如果寫鎖被占用,只有當(dāng)前線程占用了寫鎖,才能獲取到讀鎖。
3、LongAdder
在高并發(fā)場景下,直接對Integer類型的整數(shù)進行i++并不能保證操作的原子性,導(dǎo)致線程安全問題。為此,我們在juc中使用了AtomicInteger,它是一個提供原子操作的Integer類。在內(nèi)部,線程安全是通過 CAS 實現(xiàn)的。但是,當(dāng)大量線程同時訪問一個鎖時,由于大量線程未能執(zhí)行CAS操作,就會發(fā)生自旋。
導(dǎo)致CPU資源消耗過大,執(zhí)行效率低下。 Doug Lea 對此并不滿意,于是他在 JDK 1.8 中對 CAS 進行了優(yōu)化,并提供了 LongAdder,它基于 CAS 段鎖的思想。

LongAdder是基于CAS和volatile實現(xiàn)的,由Unsafe提供。 LongAdder 的 Striped64 父類中維護了一個基變量和一個Cell數(shù)組。當(dāng)多個線程對一個變量進行操作時,首先對這個基變量進行CAS操作。當(dāng)檢測到其他線程時,將使用元胞數(shù)組。
例如,當(dāng)即將更新 base 時,檢測到更多的線程。即casBase方法更新基值失敗,自動使用cell數(shù)組,每個線程對應(yīng)一個cell,在每個線程中對cell進行CAS操作。
這樣就可以在多個值之間分擔(dān)單個值的更新壓力,降低單個值的“熱度”。也減少了大量線程的自旋,提高并發(fā)效率,分散并發(fā)壓力。這種段鎖需要額外的內(nèi)存單元,但是在高并發(fā)場景下成本幾乎可以忽略不計。段鎖是一種杰出的優(yōu)化方法。 juc中的ConcurrentHashMap也是基于段鎖來保證讀寫的線程安全。
到此這篇關(guān)于淺談一下Java中的幾種JVM級別的鎖的文章就介紹到這了,更多相關(guān)JVM級別的鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
servlet之session工作原理簡介_動力節(jié)點Java學(xué)院整理
這篇文章主要介紹了servlet之session工作原理簡介,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-07-07
SpringBoot的@Value注解如何設(shè)置默認值
這篇文章主要介紹了SpringBoot的@Value注解如何設(shè)置默認值問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-02-02
springboot程序啟動慢-未配置hostname的解決
這篇文章主要介紹了springboot程序啟動慢-未配置hostname的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08

