synchronized原理及優化,(自旋鎖,鎖消除,鎖粗化,偏向鎖,輕量級鎖)


 


偏向鎖:不占用CPU
自旋鎖:占用CPU。代碼執行成本比較低且線程數少時,可以使用 。不經過OS。內核態,效率偏低

 

理解Java對象頭與Monitor

在JVM中,對象在內存中的布局分為三塊區域:對象頭、實例數據和對齊填充。如下:

 

 

 

實例變量:存放類的屬性數據信息,包括父類的屬性信息,如果是數組的實例部分還包括數組的長度,這部分內存按4字節對齊。

填充數據:由於虛擬機要求對象起始地址必須是8字節的整數倍。填充數據不是必須存在的,僅僅是為了字節對齊。

 

 

而對於頂部,則是Java頭對象,它實現synchronized的鎖對象的基礎,這點我們重點分析它,一般而言,synchronized使用的鎖對象是存儲在Java對象頭里的,jvm中采用2個字來存儲對象頭(如果對象是數組則會分配3個字,多出來的1個字記錄的是數組長度),其主要結構是由Mark Word 和 Class Metadata Address 組成,其結構說明如下表:

 

 

 

 

 

其中Mark Word在默認情況下存儲着對象的HashCode、分代年齡、鎖標記位等以下是32位JVM的Mark Word默認存儲結構

 

 

 


由於對象頭的信息是與對象自身定義的數據沒有關系的額外存儲成本,因此考慮到JVM的空間效率,Mark Word 被設計成為一個非固定的數據結構,以便存儲更多有效的數據,它會根據對象本身的狀態復用自己的存儲空間,如32位JVM下,除了上述列出的Mark Word默認存儲結構外,還有如下可能變化的結構:

 

 

 


其中輕量級鎖和偏向鎖是Java 6對 synchronized 鎖進行優化后新增加的,稍后我們會簡要分析。這里我們主要分析一下重量級鎖也就是通常說synchronized的對象鎖,鎖標識位為10,其中指針指向的是monitor對象(也稱為管程或監視器鎖)的起始地址。每個對象都存在着一個 monitor 與之關聯,對象與其 monitor 之間的關系有存在多種實現方式,如monitor可以與對象一起創建銷毀或當線程試圖獲取對象鎖時自動生成,但當一個 monitor 被某個線程持有后,它便處於鎖定狀態。

 

 

先來舉個例子,然后我們在上源碼。我們可以把監視器理解為包含一個特殊的房間的建築物,這個特殊房間同一時刻只能有一個客人(線程)。這個房間中包含了一些數據和代碼。

 

 

 

 

 

如果一個顧客想要進入這個特殊的房間,他首先需要在走廊(Entry Set)排隊等待。調度器將基於某個標准(比如 FIFO)來選擇排隊的客戶進入房間。如果,因為某些原因,該客戶暫時因為其他事情無法脫身(線程被掛起),那么他將被送到另外一間專門用來等待的房間(Wait Set),這個房間的可以可以在稍后再次進入那件特殊的房間。如上面所說,這個建築屋中一共有三個場所。

 

 

 


總之,監視器是一個用來監視這些線程進入特殊的房間的。他的義務是保證(同一時間)只有一個線程可以訪問被保護的數據和代碼。
Monitor其實是一種同步工具,也可以說是一種同步機制,它通常被描述為一個對象,主要特點是:

對象的所有方法都被“互斥”的執行。好比一個Monitor只有一個運行“許可”,任一個線程進入任何一個方法都需要獲得這個“許可”,離開時把許可歸還。
通常提供singal機制:允許正持有“許可”的線程暫時放棄“許可”,等待某個謂詞成真(條件變量),而條件成立后,當前進程可以“通知”正在等待這個條件變量的線程,讓他可以重新去獲得運行許可。
監視器的實現
在Java虛擬機(HotSpot)中,Monitor是基於C++實現的,由ObjectMonitor實現的,其主要數據結構如下:

ObjectMonitor() {
_header = NULL;
_count = 0;
_waiters = 0,
_recursions = 0;
_object = NULL;
_owner = NULL;
_WaitSet = NULL;
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
_EntryList = NULL ;
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
}
ObjectMonitor中有幾個關鍵屬性:

_owner:指向持有ObjectMonitor對象的線程

_WaitSet:存放處於wait狀態的線程隊列

_EntryList:存放處於等待鎖block狀態的線程隊列

_recursions:鎖的重入次數

_count:用來記錄該線程獲取鎖的次數

當多個線程同時訪問一段同步代碼時,首先會進入_EntryList隊列中,當某個線程獲取到對象的monitor后進入_Owner區域並把monitor中的_owner變量設置為當前線程,同時monitor中的計數器_count加1。即獲得對象鎖。

 

 

若持有monitor的線程調用wait()方法,將釋放當前持有的monitor,_owner變量恢復為null,_count自減1,同時該線程進入_WaitSet集合中等待被喚醒。若當前線程執行完畢也將釋放monitor(鎖)並復位變量的值,以便其他線程進入獲取monitor(鎖)。如下圖所示

 

 

 

 

ObjectMonitor類中提供了幾個方法:

獲得鎖
void ATTR ObjectMonitor::enter(TRAPS) {
Thread * const Self = THREAD ;
void * cur ;
//通過CAS嘗試把monitor的`_owner`字段設置為當前線程
cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;
//獲取鎖失敗
if (cur == NULL) { assert (_recursions == 0 , "invariant") ;
assert (_owner == Self, "invariant") ;
// CONSIDER: set or assert OwnerIsThread == 1
return ;
}
//如果舊值和當前線程一樣,說明當前線程已經持有鎖,此次為重入,_recursions自增,並獲得鎖。
if (cur == Self) {
// TODO-FIXME: check for integer overflow! BUGID 6557169.
_recursions ++ ;
return ;
}
//如果當前線程是第一次進入該monitor,設置_recursions為1,_owner為當前線程
if (Self->is_lock_owned ((address)cur)) {
assert (_recursions == 0, "internal state error");
_recursions = 1 ;
// Commute owner from a thread-specific on-stack BasicLockObject address to
// a full-fledged "Thread *".
_owner = Self ;
OwnerIsThread = 1 ;
return ;
}
//省略部分代碼。
//通過自旋執行ObjectMonitor::EnterI方法等待鎖的釋放
for (;;) {
jt->set_suspend_equivalent();
// cleared by handle_special_suspend_equivalent_condition()
// or java_suspend_self()
EnterI (THREAD) ;
if (!ExitSuspendEquivalent(jt)) break ;
//
// We have acquired the contended monitor, but while we were
// waiting another thread suspended us. We don't want to enter
// the monitor while suspended because that would surprise the
// thread that suspended us.
//
_recursions = 0 ;
_succ = NULL ;
exit (Self) ;
jt->java_suspend_self();
}
}

 

 


釋放鎖
void ATTR ObjectMonitor::exit(TRAPS) {
Thread * Self = THREAD ;
//如果當前線程不是Monitor的所有者
if (THREAD != _owner) {
if (THREAD->is_lock_owned((address) _owner)) { //
// Transmute _owner from a BasicLock pointer to a Thread address.
// We don't need to hold _mutex for this transition.
// Non-null to Non-null is safe as long as all readers can
// tolerate either flavor.
assert (_recursions == 0, "invariant") ;
_owner = THREAD ;
_recursions = 0 ;
OwnerIsThread = 1 ;
} else {
// NOTE: we need to handle unbalanced monitor enter/exit
// in native code by throwing an exception.
// TODO: Throw an IllegalMonitorStateException ?
TEVENT (Exit - Throw IMSX) ;
assert(false, "Non-balanced monitor enter/exit!");
if (false) {
THROW(vmSymbols::java_lang_IllegalMonitorStateException());
}
return;
}
}
//如果_recursions次數不為0.自減
if (_recursions != 0) {
_recursions--; // this is simple recursive enter
TEVENT (Inflated exit - recursive) ;
return ;
}
 

 

省略部分代碼,根據不同的策略(由QMode指定),從cxq或EntryList中獲取頭節點,通過ObjectMonitor::ExitEpilog方法喚醒該節點封裝的線程,喚醒操作最終由unpark完成。

 

 

 

 

由此看來,monitor對象存在於每個Java對象的對象頭中(存儲的指針的指向),synchronized鎖便是通過這種方式獲取鎖的,也是為什么Java中任意對象可以作為鎖的原因,同時也是notify/notifyAll/wait等方法存在於頂級對象Object中的原因,在使用這3個方法時,必須處於synchronized代碼塊或者synchronized方法中,否則就會拋出IllegalMonitorStateException異常,這是因為調用這幾個方法前必須拿到當前對象的監視器monitor對象,也就是說notify/notifyAll和wait方法依賴於monitor對象,在前面的分析中,我們知道monitor 存在於對象頭的Mark Word 中(存儲monitor引用指針),而synchronized關鍵字可以獲取 monitor ,這也就是為什么notify/notifyAll和wait方法必須在synchronized代碼塊或者synchronized方法調用的原因。下面我們將進一步分析synchronized在字節碼層面的具體語義實現。

synchronized底層原理
public class SynchronizedDemo {
//同步方法
public synchronized void doSth(){
System.out.println("Hello World");
}

//同步代碼塊
public void doSth1(){
synchronized (SynchronizedDemo.class){
System.out.println("Hello World");
}
}
}
被synchronized修飾的代碼塊及方法,在同一時間,只能被單個線程訪問。

synchronized的實現原理

synchronized,是Java中用於解決並發情況下數據同步訪問的一個很重要的關鍵字。當我們想要保證一個共享資源在同一時間只會被一個線程訪問到時,我們可以在代碼中使用synchronized關鍵字對類或者對象加鎖。
我們對上面的代碼進行反編譯,可以得到如下代碼:

public synchronized void doSth();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String Hello World
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
public void doSth1();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: ldc #5 // class com/hollis/SynchronizedTest
2: dup
3: astore_1
4: monitorenter
5: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
8: ldc #3 // String Hello World
10: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
13: aload_1
14: monitorexit
15: goto 23
18: astore_2
19: aload_1
20: monitorexit
21: aload_2
22: athrow
23: return
通過反編譯后代碼可以看出:對於同步方法,JVM采用ACC_SYNCHRONIZED標記符來實現同步。 對於同步代碼塊。JVM采用monitorenter、monitorexit兩個指令來實現同步。

方法級的同步是隱式的。同步方法的常量池中會有一個ACC_SYNCHRONIZED標志。當某個線程要訪問某個方法的時候,會檢查是否有ACC_SYNCHRONIZED,如果有設置,則需要先獲得監視器鎖,然后開始執行方法,方法執行之后再釋放監視器鎖。這時如果其他線程來請求執行方法,會因為無法獲得監視器鎖而被阻斷住。值得注意的是,如果在方法執行過程中,發生了異常,並且方法內部並沒有處理該異常,那么在異常被拋到方法外面之前監視器鎖會被自動釋放。

同步代碼塊使用monitorenter和monitorexit兩個指令實現。可以把執行monitorenter指令理解為加鎖,執行monitorexit理解為釋放鎖。 每個對象維護着一個記錄着被鎖次數的計數器。未被鎖定的對象的該計數器為0,當一個線程獲得鎖(執行monitorenter)后,該計數器自增變為 1 ,當同一個線程再次獲得該對象的鎖的時候,計數器再次自增。當同一個線程釋放鎖(執行monitorexit指令)的時候,計數器再自減。當計數器為0的時候。鎖將被釋放,其他線程便可以獲得鎖。

無論是ACC_SYNCHRONIZED還是monitorenter、monitorexit都是基於Monitor實現的,在Java虛擬機(HotSpot)中,Monitor是基於C++實現的,由ObjectMonitor實現。

ObjectMonitor類中提供了幾個方法,如enter、exit、wait、notify、notifyAll等。sychronized加鎖的時候,會調用objectMonitor的enter方法,解鎖的時候會調用exit方法。sychronized加鎖的時候,會調用objectMonitor的enter方法,解鎖的時候會調用exit方法。事實上,只有在JDK1.6之前,synchronized的實現才會直接調用ObjectMonitor的enter和exit,這種鎖被稱之為重量級鎖。為什么說這種方式操作鎖很重呢?

Java的線程是映射到操作系統原生線程之上的,如果要阻塞或喚醒一個線程就需要操作系統的幫忙,這就要從用戶態轉換到核心態,因此狀態轉換需要花費很多的處理器時間,對於代碼簡單的同步塊(如被synchronized修飾的get 或set方法)狀態轉換消耗的時間有可能比用戶代碼執行的時間還要長,所以說synchronized是java語言中一個重量級的操縱。

所以,在JDK1.6中出現對鎖進行了很多的優化,進而出現輕量級鎖,偏向鎖,鎖消除,適應性自旋鎖,鎖粗化(自旋鎖在1.4就有 只不過默認的是關閉的,jdk1.6是默認開啟的),這些操作都是為了在線程之間更高效的共享數據,解決競爭問題。

Java虛擬機對synchronized的優化
鎖的狀態總共有四種,無鎖狀態、偏向鎖、輕量級鎖和重量級鎖。隨着鎖的競爭,鎖可以從偏向鎖升級到輕量級鎖,再升級的重量級鎖,但是鎖的升級是單向的,也就是說只能從低到高升級,不會出現鎖的降級,關於重量級鎖,前面我們已詳細分析過,下面我們將介紹偏向鎖和輕量級鎖以及JVM的其他優化手段。

自旋鎖與自適應自旋
 

 

 

 

 

前面我們討論互斥同步的時候,提到了互斥同步對性能最大的影響是阻塞的實現,掛起線程和恢復線程的操作都需要轉入內核態中完成,這些操作給系統的並發性能帶來了很大的壓力。同時,虛擬機的開發團隊也注意到在許多應用上,共享數據的鎖定狀態只會持續很短的一段時間,為了這段時間去掛起和恢復線程並不值得。如果物理機器有一個以上的處理器,能讓兩個或以上的線程同時並行執行,我們就可以讓后面請求鎖的那個線程“稍等一 下”,但不放棄處理器的執行時間,看看持有鎖的線程是否很快就會釋放鎖。為了讓線程等待,我們只需讓線程執行一個忙循環(自旋),這項技術就是所謂的自旋鎖。

自旋鎖在JDK 1.4.2中就已經引入,只不過默認是關閉的,可以使用 :XX:+UseSpinning 參數來開啟,在JDK 1.6 中就已經改為默認開啟了。自旋等待不能代替阻塞,且先不說對處理器數量的要求,自旋等待本身雖然避免了線程切換的開銷,但它是要占用處理器時間的,因此,如果鎖被占用的時間很短,自旋等待的效果就會非常好,反之,如果鎖被占用的時間很長,那么自旋的線程只會白白消耗處理器資源,而不會做任何有用的工作,反而會帶來性能上的浪費。因此,自旋等待的時間必須要有一定的限度,如果自旋超過了限定的次數仍然沒有成功獲得鎖,就應當使用傳統的方式去掛起線程了。自旋次數的默認值是 10 次,用戶可以使用參數-XX:PreBlockSpin 來更改。

在JDK 1.6中引入了自適應的自旋鎖。自適應意味着自旋的時間不再固定了,而是由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態來決定。如果在同一個鎖對象上,自旋等待剛剛成功獲得過鎖,並且持有鎖的線程正在運行中,那么虛擬機就會認為這次自旋也很有可能再次成功,進而它將允許自旋等待持續相對更長的時間,比如100個循環。另外,如果對於某個鎖,自旋很少成功獲得過,那在以后要獲取這個鎖時將可能省略掉自旋過程,以避免浪費處理部資源。有了自適應自旋,隨着程序運行和性能監控信息的不斷完善,虛擬機對程序鎖的狀況預測就會越來越准確。虛擬機就會變得越來越 “聰明” 了。

鎖消除
鎖消除是指虛擬機即時編譯器在運行時,對一些代碼上要求同步,但是被檢測到不可能存在共享數據競爭的鎖進行消除。鎖消除的主要判定依據來源於逃逸分析的數據支持,如果判斷在一段代碼中,堆上的所有數據都不會逃逸出去從而被其他線程訪問到,那就可以把它們當做棧上數據對待,認為它們是線程私有的,同步加鎖自然就無須進行。

也許讀者會有疑問,變量是否逃逸,對於虛擬機來說需要使用數據流分析來確定,但是程序員自己所該是很清楚的,怎么會在明知道不存在數據爭用的情況下要求同步呢?答案是有許多同步措施並不是程序員自己加入的,同步的代碼在Java程序中的普遍程度也許超過了 大部分讀者的想象。我們來看看下面代碼中的例子,這段非常簡單的代碼僅僅是輸出 3 個字符串相加的結果,無論是源碼字面上還是程序語義上都沒有同步。

 

 

 

我們也知道,由於 String 是一個不可變的類,對字符串的連接操作總是通過生成新的String 對象來進行的,因此 Javac 編譯器會對 String 連接做自動優化。在 JDK 1.5 之前,會轉化為 StringBuffer 對象的連續 append()操作,在JDK 1.5及以后的版本中,會轉化為 StringBuilder 對象的連續 append()操作,上述代碼可能會變成下面的樣子。

 

 

 


現在大家還認為這段代碼沒有涉及同步嗎?每個StringBuffer.append() 方法中都有一個同步塊,鎖就是 sb 對象。虛擬機觀察變量sb,很快就會發現它的動態作用域被限制在 concatString() 方法內部。也就是說,sb 的所有引用永遠不會 “逃逸” 到 concatString()方法之外,其他線程無法訪問到它,因此,雖然這里有鎖,但是可以被安全地消除掉,在即時編譯之后,這段代碼就會忽略掉所有的同步而直接執行了。

鎖粗化

 

 


原則上,我們在編寫代碼的時候,總是推薦將同步塊的作用范圍限制得盡量小,只在共享數據的實際作用域中才進行同步,這樣是為了使得需要同步的操作數量盡可能變小,如果存在鎖競爭,那等待鎖的線程也能盡快拿到鎖。

大部分情況下,上面的原則都是正確的,但是如果一系列的連續操作都對同一個對象反復加鎖和解鎖,甚至加鎖操作是出現在循環體中的,那即使沒有線程競爭,頻繁地進行互斥同步操作也會導致不必要的性能損耗。

 

 

 

上述代碼中連續的append()方法就屬於這類情況。如果虛擬機探測到有這樣一串零碎的操作都對同一個對象加鎖,將會把加鎖同步的范圍擴展 (粗化)到整個操作序列的外部,以上述代碼為例,就是擴展到 append()操作外部,也就是把while循環加鎖,這樣只需要加鎖一次就可以了。

輕量級鎖

 

 


倘若偏向鎖失敗,虛擬機並不會立即升級為重量級鎖,它還會嘗試使用一種稱為輕量級鎖的優化手段(1.6之后加入的),此時Mark Word 的結構也變為輕量級鎖的結構。輕量級鎖能夠提升程序性能的依據是“對絕大部分的鎖,在整個同步周期內都不存在競爭”,注意這是經驗數據。需要了解的是,輕量級鎖所適應的場景是線程交替執行同步塊的場合,如果存在同一時間訪問同一鎖的場合,就會導致輕量級鎖膨脹為重量級鎖。

輕盤級鎖是JDK1.6之中加入的新型鎖機制,它名字中的 “輕量級” 是相對於使用操作系統互斥量來實現的傳統鎖而言的,因此傳統的鎖機制就稱為 “重量級” 鎖。首先需要強調一點的是,輕量級鎖並不是用來代替重量級鎖的,它的本意是在沒有多線程競爭的前提下,減少傳統的重量級鎖使用操作系統互斥量產生的性能消耗。

在代碼進入同步塊的時候,如果此同步對象沒有被鎖定(鎖標志位為“01”狀態),虛擬機首先將在當前線程的棧幀中建立一個名為鎖記錄(Lock Record)的空間,用於存儲鎖對象目前的 Mark Word 的拷貝 (官方把這份拷貝加了一個 Displaced 前轍,即Displaced Mark Word ),這時候線程堆棧與對象頭的狀態如圖所示。

然后,虛擬機將使用CAS操作嘗試將對象的 Mark Word 更新為指向 Lock Record 的指針。如果這個更新動作成功了,那么這個線程就擁有了該對象的鎖,並且對象 Mark Word 的鎖標志位(Mark Word 的最后 2bit )將轉變為 “00”,即表示此對象處於輕量級鎖定狀態,這時候線程堆棧與對象頭的狀態如圖所示。

 

如果這個更新操作失敗了,虛擬機首先會檢查對象的Mark Word是否指向當前線程的棧幀,如果是說明當前線程已經擁有了這個對象的鎖,那就可以直接進入同步塊繼續執行,否則說明這個鎖對象已經被其他線程搶占了。如果有兩條以上的線程爭用同一個鎖,那輕量級鎖就不再有效,要膨脹為重量級鎖,鎖標志的狀態值變為“10”,Mark Word 中存儲的就是指向重量級鎖(互斥量)的指針,后而等待鎖的線程也要進入阻塞狀態。

上而描述的是輕量級鎖的加鎖過程,它的解鎖過程也是通過CAS操作來進行的,如果對象的 Mark Word 仍然指向着線程的鎖記錄,那就用 CAS 操作把對象當前的 Mark Word 和 線程中復制的 Displaced Mark Word 替換回來,如果替換成功,越個同步過程就完成了。如果替換失敗,說明有其他線程嘗試過獲取該鎖,那就要在釋放鎖的同時,喚醒被掛起的線程。

輕量級鎖能提升程序同步性能的依據是“對於絕大部分的鎖,在整個同步周期內都是不存在競爭的”,這是一個經驗數據。如果沒有競爭,輕量級鎖使用 CAS 操作避免了使用互斥量的開銷,但如果存在鎖競爭,除了互斥量的開銷外,還額外發生了 CAS 操作,因此在有競爭的情況下,輕量級鎖會比傳統的重量級鎖更慢。

偏向鎖

 

 


偏向鎖是Java 6之后加入的新鎖,它是一種針對加鎖操作的優化手段,經過研究發現,在大多數情況下,鎖不僅不存在多線程競爭,而且總是由同一線程多次獲得,因此為了減少同一線程獲取鎖(會涉及到一些CAS操作,耗時)的代價而引入偏向鎖。偏向鎖的核心思想是,如果一個線程獲得了鎖,那么鎖就進入偏向模式,此時Mark Word 的結構也變為偏向鎖結構,當這個線程再次請求鎖時,無需再做任何同步操作,即獲取鎖的過程,這樣就省去了大量有關鎖申請的操作,從而也就提供程序的性能。所以,對於沒有鎖競爭的場合,偏向鎖有很好的優化效果,畢竟極有可能連續多次是同一個線程申請相同的鎖。但是對於鎖競爭比較激烈的場合,偏向鎖就失效了,因為這樣場合極有可能每次申請鎖的線程都是不相同的,因此這種場合下不應該使用偏向鎖,否則會得不償失,需要注意的是,偏向鎖失敗后,並不會立即膨脹為重量級鎖,而是先升級為輕量級鎖。

偏向鎖的目的是消除數據在無競爭情況下的同步原語,進一步提高程序的運行性能。如果說輕量級鎖是在無競爭的情況下使用CAS操作去消除同步使用的互斥量,那偏向鎖就是在無競爭的情況下把整個同步都消除掉,連 CAS 操作都不做了。

偏向鎖的“偏”,就是偏心的 “偏”、偏袒的 “偏”,它的意思是這個鎖會偏向於第一個獲得它的線程,如果在接下來的執行過程中,該鎖沒有被其他的線程獲取,則持有偏向鎖的線程將永遠不需要再進行同步。

如果讀者讀懂了前面輕量級鎖中關於對象頭Mark Word與線程之間的操作過程,那偏向鎖的原理理解起來就會很簡單。假設當前虛擬機啟用了偏向(啟用參數-XX:+UseBiasedLocking,這是 JDK 1.6 的默認值),那么,當鎖對象第一次被線程獲取的時候,虛擬機將會把對象頭中的標志位設為 “01 ”,即偏向模式。同時使用 CAS 操作把獲取到這個鎖的線程的 ID記錄在對象的 Mark Word 之中,如果 CAS 操作成功,持有偏向鎖的錢程以后每次進入這個鎖相關的同步塊時,虛擬機都可以不再進行任何同步操作(例如 Locking 、Unlocking 及對 Mark Word的Update 等)。

當有另外一個線程去嘗試獲取這個鎖時,偏向模式就宣告結束。根據鎖對象目前是否處於被鎖定的狀態,撤銷偏向(Revoke Bias)后恢復到未鎖定(標志位為“01”) 或輕量級鎖定(標志位為 “00”)的狀態,后續的同步操作就如上面介紹的輕量級鎖那樣執行。偏向鎖、 輕量級鎖的狀態轉化及對象 Mark Word 的關系如圖所示。

 

 

 

 

 

 

同步但無競爭的程序性能。它同樣是一個帶有效益權衡(Trade Off)性質的優化,也就是說,它並不一定總是對程序運行有利,如果程序中大多數的鎖總是被多個不同的線程訪問,那偏向模式就是多余的。在具體問題具體分析的前提下,有時候使用參數XX:-UseBiasedLocking來禁止偏向鎖優化反而可以提升性能。

 

 

鎖的內存語義:
總結:

 

 

————————————————
版權聲明:本文為CSDN博主「yuan_qh」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/yuan_qh/article/details/100195185

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM