本篇內(nèi)容介紹了“java中經(jīng)典的JVM鎖有哪些”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
成都創(chuàng)新互聯(lián)是一家專業(yè)的成都網(wǎng)站建設(shè)公司,我們專注網(wǎng)站建設(shè)、網(wǎng)站制作、網(wǎng)絡(luò)營銷、企業(yè)網(wǎng)站建設(shè),賣鏈接,廣告投放為企業(yè)客戶提供一站式建站解決方案,能帶給客戶新的互聯(lián)網(wǎng)理念。從網(wǎng)站結(jié)構(gòu)的規(guī)劃UI設(shè)計到用戶體驗提高,創(chuàng)新互聯(lián)力求做到盡善盡美。
synchronized synchronized關(guān)鍵字是一把經(jīng)典的鎖,也是我們平時用得最多的。在jdk1.6之前,syncronized是一把重量級的鎖,不過隨著jdk的升級,也在對它進(jìn)行不斷的優(yōu)化,如今它變得不那么重了,甚至在某些場景下,它的性能反而優(yōu)于輕量級鎖。在加了syncronized關(guān)鍵字的方法、代碼塊中,一次只允許一個線程進(jìn)入特定代碼段,從而避免多線程同時修改同一數(shù)據(jù)。synchronized鎖有如下幾個特點(diǎn):
a、有鎖升級過程 在jdk1.5(含)之前,synchronized的底層實現(xiàn)是重量級的,所以之前一直稱呼它為"重量級鎖",在jdk1.5之后,對synchronized進(jìn)行了各種優(yōu)化,它變得不那么重了,實現(xiàn)原理就是鎖升級的過程。我們先聊聊1.5之后的synchronized實現(xiàn)原理是怎樣的。說到synchronized加鎖原理,就不得不先說java對象在內(nèi)存中的布局,java對象內(nèi)存布局如下:
如上圖所示,在創(chuàng)建一個對象后,在JVM虛擬機(jī)(HotSpot)中,對象在Java內(nèi)存中的存儲布局 可分為三塊:**(1)對象頭區(qū)域
**此處存儲的信息包括兩部分:
對象自身的運(yùn)行時數(shù)據(jù)(MarkWord)
存儲hashCode、GC分代年齡、鎖類型標(biāo)記、偏向鎖線程ID、CAS鎖指向線程LockRecord的指針等,synconized鎖的機(jī)制與這個部分(markwork)密切相關(guān),用markword中最低的三位代表鎖的狀態(tài),其中一位是偏向鎖位,另外兩位是普通鎖位
對象類型指針(Class Pointer)
對象指向它的類元數(shù)據(jù)的指針、JVM就是通過它來確定是哪個Class的實例(2)實例數(shù)據(jù)區(qū)域 此處存儲的是對象真正有效的信息,比如對象中所有字段的內(nèi)容
(3)對齊填充區(qū)域 JVM的實現(xiàn)HostSpot規(guī)定對象的起始地址必須是8字節(jié)的整數(shù)倍,換句話來說,現(xiàn)在64位的OS往外讀取數(shù)據(jù)的時候一次性讀取64bit整數(shù)倍的數(shù)據(jù),也就是8個字節(jié),所以HotSpot為了高效讀取對象,就做了"對齊",如果一個對象實際占的內(nèi)存大小不是8byte的整數(shù)倍時,就"補(bǔ)位"到8byte的整數(shù)倍。所以對齊填充區(qū)域的大小不是固定的。
當(dāng)線程進(jìn)入到synchronized處嘗試獲取該鎖時,synchronized鎖升級流程如下:
如上圖所示,synchronized鎖升級的順序為: 偏向鎖->輕量級鎖->重量級鎖,每一步觸發(fā)鎖升級的情況如下:偏向鎖在JDK1.8中,其實默認(rèn)是輕量級鎖,但如果設(shè)定了-XX:BiasedLockingStartupDelay = 0,那在對一個Object做syncronized的時候,會立即上一把偏向鎖。當(dāng)處于偏向鎖狀態(tài)時,markwork會記錄當(dāng)前線程ID升級到輕量級鎖當(dāng)下一個線程參與到偏向鎖競爭時,會先判斷markword中保存的線程ID是否與這個線程ID相等,如果不相等,會立即撤銷偏向鎖,升級為輕量級鎖。每個線程在自己的線程棧中生成一個LockRecord(LR),然后每個線程通過CAS(自旋)的操作將鎖對象頭中的markwork設(shè)置為指向自己的LR的指針,哪個線程設(shè)置成功,就意味著獲得鎖。關(guān)于synchronized中此時執(zhí)行的CAS操作是通過native的調(diào)用HotSpot中bytecodeInterpreter.cpp文件C++代碼實現(xiàn)的,有興趣的可以繼續(xù)深挖升級到重量級鎖如果鎖競爭加劇(如線程自旋次數(shù)或者自旋的線程數(shù)超過某閾值,JDK1.6之后,由JVM自己控制改規(guī)則),就會升級為重量級鎖。此時就會向操作系統(tǒng)申請資源,線程掛起,進(jìn)入到操作系統(tǒng)內(nèi)核態(tài)的等待隊列中,等待操作系統(tǒng)調(diào)度,然后映射回用戶態(tài)。在重量級鎖中,由于需要做內(nèi)核態(tài)到用戶態(tài)的轉(zhuǎn)換,而這個過程中需要消耗較多時間,也就是"重"的原因之一。
b、可重入synchronized擁有強(qiáng)制原子性的內(nèi)部鎖機(jī)制,是一把可重入鎖。因此,在一個線程使用synchronized方法時調(diào)用該對象另一個synchronized方法,即一個線程得到一個對象鎖后再次請求該對象鎖,是永遠(yuǎn)可以拿到鎖的。在Java中線程獲得對象鎖的操作是以線程為單位的,而不是以調(diào)用為單位的。synchronized鎖的對象頭的markwork中會記錄該鎖的線程持有者和計數(shù)器,當(dāng)一個線程請求成功后,JVM會記下持有鎖的線程,并將計數(shù)器計為1。此時其他線程請求該鎖,則必須等待。而該持有鎖的線程如果再次請求這個鎖,就可以再次拿到這個鎖,同時計數(shù)器會遞增。當(dāng)線程退出一個synchronized方法/塊時,計數(shù)器會遞減,如果計數(shù)器為0則釋放該鎖鎖。
c、悲觀鎖(互斥鎖、排他鎖)synchronized是一把悲觀鎖(獨(dú)占鎖),當(dāng)前線程如果獲取到鎖,會導(dǎo)致其它所有需要鎖該的線程等待,一直等待持有鎖的線程釋放鎖才繼續(xù)進(jìn)行鎖的爭搶
ReentrantLock**ReentrantLock從字面可以看出是一把可重入鎖,這點(diǎn)和synchronized一樣,但實現(xiàn)原理也與syncronized有很大差別,它是基于經(jīng)典的AQS(AbstractQueueSyncronized)實現(xiàn)的,AQS是基于volitale和CAS實現(xiàn)的,其中AQS中維護(hù)一個valitale類型的變量state來做一個可重入鎖的重入次數(shù),加鎖和釋放鎖也是圍繞這個變量來進(jìn)行的。ReentrantLock也提供了一些synchronized沒有的特點(diǎn),因此比synchronized好用AQS模型如下圖:
ReentrantLock有如下特點(diǎn):a、可重入 ReentrantLock和syncronized關(guān)鍵字一樣,都是可重入鎖,不過兩者實現(xiàn)原理稍有差別,RetrantLock利用AQS的的state狀態(tài)來判斷資源是否已鎖,同一線程重入加鎖,state的狀態(tài)+1; 同一線程重入解鎖,state狀態(tài)-1(解鎖必須為當(dāng)前獨(dú)占線程,否則異常); 當(dāng)state為0時解鎖成功。b、需要手動加鎖、解鎖 synchronized關(guān)鍵字是自動進(jìn)行加鎖、解鎖的,而ReentrantLock需要lock()和unlock()方法配合try/finally語句塊來完成,來手動加鎖、解鎖c、支持設(shè)置鎖的超時時間 synchronized關(guān)鍵字無法設(shè)置鎖的超時時間,如果一個獲得鎖的線程內(nèi)部發(fā)生死鎖,那么其他線程就會一直進(jìn)入阻塞狀態(tài),而ReentrantLock提供tryLock方法,允許設(shè)置線程獲取鎖的超時時間,如果超時,則跳過,不進(jìn)行任何操作,避免死鎖的發(fā)生d、支持公平/非公平鎖 synchronized關(guān)鍵字是一種非公平鎖,先搶到鎖的線程先執(zhí)行。而ReentrantLock的構(gòu)造方法中允許設(shè)置true/false來實現(xiàn)公平、非公平鎖,如果設(shè)置為true,則線程獲取鎖要遵循"先來后到"的規(guī)則,每次都會構(gòu)造一個線程N(yùn)ode,然后到雙向鏈表的"尾巴"后面排隊,等待前面的Node釋放鎖資源。e、可中斷鎖ReentrantLock中的lockInterruptibly()方法使得線程可以在被阻塞時響應(yīng)中斷,比如一個線程t1通過lockInterruptibly()方法獲取到一個可重入鎖,并執(zhí)行一個長時間的任務(wù),另一個線程通過interrupt()方法就可以立刻打斷t1線程的執(zhí)行,來獲取t1持有的那個可重入鎖。而通過ReentrantLock的lock()方法或者Synchronized持有鎖的線程是不會響應(yīng)其他線程的interrupt()方法的,直到該方法主動釋放鎖之后才會響應(yīng)interrupt()方法。
ReentrantReadWriteLockReentrantReadWriteLock(讀寫鎖)其實是兩把鎖,一把是WriteLock(寫鎖),一把是讀鎖,ReadLock。讀寫鎖的規(guī)則是:讀讀不互斥、讀寫互斥、寫寫互斥。在一些實際的場景中,讀操作的頻率遠(yuǎn)遠(yuǎn)高于寫操作,如果直接用一般的鎖進(jìn)行并發(fā)控制的話,就會讀讀互斥、讀寫互斥、寫寫互斥,效率低下,讀寫鎖的產(chǎn)生就是為了優(yōu)化這種場景的操作效率。一般情況下獨(dú)占鎖的效率低來源于高并發(fā)下對臨界區(qū)的激烈競爭導(dǎo)致線程上下文切換。因此當(dāng)并發(fā)不是很高的情況下,讀寫鎖由于需要額外維護(hù)讀鎖的狀態(tài),可能還不如獨(dú)占鎖的效率高。因此需要根據(jù)實際情況選擇使用。ReentrantReadWriteLock的原理也是基于AQS進(jìn)行實現(xiàn)的,與ReentrantLock的差別在于ReentrantReadWriteLock鎖擁有共享鎖、排他鎖屬性。讀寫鎖中的加鎖、釋放鎖也是基于Sync(繼承于AQS),并且主要使用AQS中的state和node中的waitState變量進(jìn)行實現(xiàn)的。實現(xiàn)讀寫鎖與實現(xiàn)普通互斥鎖的主要區(qū)別在于需要分別記錄讀鎖狀態(tài)及寫鎖狀態(tài),并且等待隊列中需要區(qū)別處理兩種加鎖操作。ReentrantReadWriteLock中將AQS中的int類型的state分為高16位與第16位分別記錄讀鎖和寫鎖的狀態(tài),如下圖所示:
a、WriteLock(寫鎖)是悲觀鎖(排他鎖、互斥鎖)通過計算 state&((1<<16)-1),將state的高16位全部抹去,因此state的低位記錄著寫鎖的重入計數(shù)
獲取寫鎖源碼:
/** * 獲取寫鎖 Acquires the write lock. * 如果此時沒有任何線程持有寫鎖或者讀鎖,那么當(dāng)前線程執(zhí)行CAS操作更新status, * 若更新成功,則設(shè)置讀鎖重入次數(shù)為1,并立即返回 *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,并立即返回 *
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)度并進(jìn)入休眠狀態(tài), * 直到該寫鎖被釋放,且成功將寫鎖持有次數(shù)設(shè)置為1才表示獲取寫鎖成功 *
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); }/** * 該方法為以獨(dú)占模式獲取鎖,忽略中斷 * 如果調(diào)用一次該“tryAcquire”方法更新status成功,則直接返回,代表搶鎖成功 * 否則,將會進(jìn)入同步隊列等待,不斷執(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ù)"寫鎖獨(dú)占"原則,返回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; }
釋放寫鎖源碼:
/* * 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,進(jìn)行GC setExclusiveOwnerThread(null); //更新寫鎖的重入計數(shù) setState(nextc); return free; }
b、ReadLock(讀鎖)是共享鎖(樂觀鎖)通過計算 state>>>16 進(jìn)行無符號補(bǔ)0,右移16位,因此state的高位記錄著寫鎖的重入計數(shù) 讀鎖獲取鎖的過程比寫鎖稍微復(fù)雜些,首先判斷寫鎖是否為0并且當(dāng)前線程不占有獨(dú)占鎖,直接返回;否則,判斷讀線程是否需要被阻塞并且讀鎖數(shù)量是否小于最大值并且比較設(shè)置狀態(tài)成功,若當(dāng)前沒有讀鎖,則設(shè)置第一個讀線程firstReader和firstReaderHoldCount;若當(dāng)前線程線程為第一個讀線程,則增加firstReaderHoldCount;否則,將設(shè)置當(dāng)前線程對應(yīng)的HoldCounter對象的值,更新成功后會在firstReaderHoldCount中readHolds(ThreadLocal類型的)的本線程副本中記錄當(dāng)前線程重入數(shù),這是為了實現(xiàn)jdk1.6中加入的getReadHoldCount()方法的,這個方法能獲取當(dāng)前線程重入共享鎖的次數(shù)(state中記錄的是多個線程的總重入次數(shù)),加入了這個方法讓代碼復(fù)雜了不少,但是其原理還是很簡單的:如果當(dāng)前只有一個線程的話,還不需要動用ThreadLocal,直接往firstReaderHoldCount這個成員變量里存重入數(shù),當(dāng)有第二個線程來的時候,就要動用ThreadLocal變量readHolds了,每個線程擁有自己的副本,用來保存自己的重入數(shù)。
獲取讀鎖源碼:
/** * 獲取讀鎖 * Acquires the read lock. * 如果寫鎖未被其他線程持有,執(zhí)行CAS操作更新status值,獲取讀鎖后立即返回 *Acquires the read lock if the write lock is not held by * another thread and returns immediately. * * 如果寫鎖被其他線程持有,那么停止該線程的CPU調(diào)度并進(jìn)入休眠狀態(tài),直到該讀鎖被釋放 *
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成功,則直接返回,代表搶鎖成功 * 否則,將會進(jìn)入同步隊列等待,不斷執(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í)行失敗 * 那就進(jìn)入到fullTryAcquireShared方法進(jìn)行死循環(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條件有一個都不滿足,則進(jìn)入到這個方法里進(jìn)行死循環(huán)重新獲取 return fullTryAcquireShared(current); } /** * 用于處理CAS操作state失敗和tryAcquireShared中未執(zhí)行獲取可重入鎖動作的full方法(補(bǔ)償方法?) * 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ù)即可。 無論何種情況,都會進(jìn)入死循環(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)前正在運(yùn)行的線程的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)) // 比較并進(jìn)行設(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; } }
通過分析可以看出:在線程持有讀鎖的情況下,該線程不能取得寫鎖(因為獲取寫鎖的時候,如果發(fā)現(xiàn)當(dāng)前的讀鎖被占用,就馬上獲取失敗,不管讀鎖是不是被當(dāng)前線程持有)。在線程持有寫鎖的情況下,該線程可以繼續(xù)獲取讀鎖(獲取讀鎖時如果發(fā)現(xiàn)寫鎖被占用,只有寫鎖沒有被當(dāng)前線程占用的情況才會獲取失敗)。
LongAdder在高并發(fā)的情況下,我們對一個Integer類型的整數(shù)直接進(jìn)行i++的時候,無法保證操作的原子性,會出現(xiàn)線程安全的問題。為此我們會用juc下的AtomicInteger,它是一個提供原子操作的Interger類,內(nèi)部也是通過CAS實現(xiàn)線程安全的。但當(dāng)大量線程同時去訪問時,就會因為大量線程執(zhí)行CAS操作失敗而進(jìn)行空旋轉(zhuǎn),導(dǎo)致CPU資源消耗過多,而且執(zhí)行效率也不高。Doug Lea大神應(yīng)該也不滿意,于是在JDK1.8中對CAS進(jìn)行了優(yōu)化,提供了LongAdder,它是基于了CAS分段鎖的思想實現(xiàn)的。線程去讀寫一個LongAdder類型的變量時,流程如下:
LongAdder也是基于Unsafe提供的CAS操作+valitale去實現(xiàn)的。在LongAdder的父類Striped64中維護(hù)著一個base變量和一個cell數(shù)組,當(dāng)多個線程操作一個變量的時候,先會在這個base變量上進(jìn)行cas操作,當(dāng)它發(fā)現(xiàn)線程增多的時候,就會使用cell數(shù)組。比如當(dāng)base將要更新的時候發(fā)現(xiàn)線程增多(也就是調(diào)用casBase方法更新base值失?。敲此鼤詣邮褂胏ell數(shù)組,每一個線程對應(yīng)于一個cell,在每一個線程中對該cell進(jìn)行cas操作,這樣就可以將單一value的更新壓力分擔(dān)到多個value中去,降低單個value的 “熱度”,同時也減少了線程大量線程的空轉(zhuǎn),提高并發(fā)效率,分散并發(fā)壓力。這種分段鎖需要額外維護(hù)一個內(nèi)存空間cells,不過在高并發(fā)場景下,這點(diǎn)成本幾乎可以忽略。分段鎖是一種優(yōu)秀的優(yōu)化思想,juc中提供的的ConcurrentHashMap也是基于分段鎖保證讀寫操作的線程安全。
“java中經(jīng)典的JVM鎖有哪些”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!