Java執行緒之鎖研究
JDK1.5以後,在鎖機制方面引入了新的鎖-Lock,在網上的說法都比較籠統,結合網上的資訊和我的理解這裡做個總結。
JAVA現有的鎖機制有兩種實現方式。JDK1.4前是通過synchronized實現。JDK1.5後加入java.util.concurrent.locks包下的各種lock。先看一看程式碼層的區別。
synchronized
在程式碼中synchronized類似“物件導向”,修飾類、方法、物件。
Lock
不作為修飾,類似“程式導向”,在方法中需要鎖的時候lock,在結束的時候unlock(一般都在finally裡)。
public void method1() {
// 舊鎖,無需人工釋放
synchronized(this){
System.out.println(1);
}
}
public void method2() {
Lock lock = new ReentrantLock();
//上鎖
lock.lock();
try{
System.out.println(2);
}finally{
// 解鎖
lock.unlock();
}
}
其次說說效能。
相關的效能測試網上已經有很多,直接拿來主義給出結論:
在併發高時lock效能優勢很明顯,在低併發時,synchronized也能取得優勢。具體的臨界範圍比較難定論,下面會討論。
現在來分析具體的區別。
鎖都是原子性的,也可以理解為鎖是否在使用的標記,並且比較和設定這個標記的操作是原子性的,不同硬體平臺上的jdk實現鎖的相關方法都是native的,比如park/unpark,所
以不同平臺上鎖的精確度的等級由這些native的方法決定。所以網上經常可以看見的結論是Lock比synchronized有更精確的原子操作,說的也是native方法,不得不感慨C才是硬體王道。
下面繼續討論怎麼由程式碼層到native的過程。
①所有物件都自動含有單一的鎖,JVM負責跟蹤物件被加鎖的次數。如果一個物件被解鎖,其計數變為0。在任務(執行緒)第一次給物件加鎖的時候,計數變為1。每當這個相同的任務(執行緒)在此物件上獲得鎖時,計數會遞增。只有首先獲得鎖的任務(執行緒)才能繼續獲取該物件上的多個鎖。每當任務離開時,計數遞減,當計數為0的時候,鎖被完全釋放。synchronized就是基於這個原理,同時synchronized靠某個物件的單一鎖技術的次數來判斷是否被鎖,所以無需(也不能)人工干預鎖的獲取和釋放。如果結合方法呼叫時的棧和框架(如果對方法的呼叫過程不熟悉建議看看http://wupuyuan.iteye.com/blog/1157548),不難推測出synchronized原理是基於棧中的某物件來控制一個框架,所以對於synchronized有常用的優化是鎖物件不鎖方法。實際上synchronized作用於方法時,鎖住的是this,作用於靜態方法/屬性時,鎖住的是存在於永久帶的CLASS,相當於這個CLASS的全域性鎖,鎖作用於一般物件時,鎖住的是對應程式碼塊。在HotSpot中JVM實現中,鎖有個專門的名字:物件監視器。當多個執行緒同時請求某個物件監視器時,物件監視器會設定幾種狀態用來區分請求的執行緒:
Contention List
所有請求鎖的執行緒將被首先放置到該競爭佇列,是個虛擬佇列,不是實際的Queue的資料結構。
Entry List
EntryList與ContentionList邏輯上同屬等待佇列,ContentionList會被執行緒併發訪問,為了降低對ContentionList隊尾的爭用,而建立EntryList。,Contention Lis中那些有資格成為候選人的執行緒被移到Entry List。
Wait Set
那些呼叫wait方法被阻塞的執行緒被放置到Wait Set
OnDeck
任何時刻最多隻能有一個執行緒正在競爭鎖,該執行緒稱為OnDeck
Owner
獲得鎖的執行緒稱為Owner
!Owner
釋放鎖的執行緒
②Lock不同於synchronized物件導向,它基於棧中的框架而不是某個具體物件,所以Lock只需要在棧裡設定鎖的開始和結束(lock和unlock)的地方就行了(人工必須標明),不用關心框架大小物件的變化等等。這麼做的好處是Lock能提供無條件的、可輪詢的、定時的、可中斷的鎖獲取操作,相對於synchronized來說,synchronized的鎖的獲取是釋放必須在一個模組裡,獲取和釋放的順序必須相反,而Lock則可以在不同範圍內獲取釋放,並且順序無關。
java.util.concurrent.locks下的鎖類很類似,依賴於java.util.concurrent.AbstractQueuedSynchronizer,它們把所有的Lock介面操作都轉嫁到Sync類上,這個類繼承了AbstractQueuedSynchronizer,它同時還包含子2個類:NonfairSync 和FairSync 從名字上可以看的出是為了實現公平和非公平性。AbstractQueuedSynchronizer中把所有的的請求執行緒構成一個佇列(一樣也是虛擬的),具體的實現可以參考http://blog.csdn.net/chen77716/article/details/6641477。
③從jdk的原始碼來看,Lock和synchronized的原始碼基本相同,區別主要在維護的同步佇列上。再往下深究就到了native方法了。
④還有個改進其實很重要的。執行緒分阻塞(wait)和非阻塞狀態,阻塞狀態由作業系統(linux、windows等)完成,當前一個被“鎖”的執行緒執行完畢後,有可能在後續的執行緒佇列裡還沒分配出一個獲取鎖而被“喚醒”的非阻塞執行緒,即所有執行緒還都是阻塞狀態時,就被系統排程(進入核心的執行緒是阻塞的),這樣會導致核心在使用者態和核心態之間來回接換,嚴重影響鎖的效能。在jdk1.6以前主要靠自旋鎖來解決,原理是在前一個執行緒結束後,爭用執行緒可以做一個空迴圈,繼續佔有CPU,等待取鎖的機會。當然這樣做顯然也是浪費時間,只是在兩種浪費中選取浪費少的……
jdk1.6後引入了偏向鎖,當執行緒第一次獲得了監視物件,之後讓監視物件“偏向”這個執行緒,之後的多次呼叫則可以避免CAS操作,等於是置了一臨時變數來記錄位置(類似索引比較)。詳細的就涉及到彙編指令了,我也就沒太深究,偏向鎖效能優於自旋鎖,但是還是沒有達到HotSpot認為的最佳時間(一個執行緒上下文切換的時間)。
綜合來看對於所有的高併發情況,採用Lock加鎖是最優選擇,但是由於歷史遺留等問題,synchronized也還是不能完全被淘汰,同時,在低併發情況下,synchronized的效能還比Lock好的。
原帖地址:http://wupuyuan.iteye.com/blog/1158655
相關文章
- Java多執行緒(2)執行緒鎖Java執行緒
- Java執行緒:執行緒的同步與鎖Java執行緒
- Java 多執行緒之內建鎖與顯示鎖Java執行緒
- java 執行緒鎖物件鎖的理解Java執行緒物件
- Java 執行緒安全 與 鎖Java執行緒
- java多執行緒–同步鎖Java執行緒
- Java多執行緒-無鎖Java執行緒
- python之執行緒鎖Python執行緒
- GCD 之執行緒死鎖GC執行緒
- Java多執行緒之執行緒中止Java執行緒
- java之執行緒Java執行緒
- Java執行緒面試題(02) Java執行緒中如何避免死鎖Java執行緒面試題
- Java多執行緒中執行緒安全與鎖問題Java執行緒
- JAVA多執行緒詳解(3)執行緒同步和鎖Java執行緒
- Java多執行緒(五):死鎖Java執行緒
- java多執行緒(5)死鎖Java執行緒
- Java多執行緒7:死鎖Java執行緒
- Java 實現執行緒死鎖Java執行緒
- Linux之執行緒互斥鎖Linux執行緒
- java多執行緒系列之執行緒池Java執行緒
- 執行緒同步及執行緒鎖執行緒
- Java多執行緒 -- 公平鎖和非公平鎖Java執行緒
- java多執行緒之執行緒的基本使用Java執行緒
- java--多執行緒之後臺執行緒Java執行緒
- 【JAVA】【面試】【基礎篇】- 執行緒、鎖Java面試執行緒
- JAVA多執行緒與鎖機制Java執行緒
- Java執行緒狀態及同步鎖Java執行緒
- Java併發(十六)----執行緒八鎖Java執行緒
- java執行緒的狀態+鎖分析Java執行緒
- java執行緒之守護執行緒和使用者執行緒Java執行緒
- 多執行緒之8鎖問題執行緒
- java 執行緒淺解03[執行緒同步以及經典死鎖]Java執行緒
- Java多執行緒/併發06、執行緒鎖Lock與ReadWriteLockJava執行緒
- 畫江湖之 PHP 多執行緒開發 【執行緒安全 互斥鎖】PHP執行緒
- 畫江湖之 PHP 多執行緒開發 [執行緒安全 互斥鎖]PHP執行緒
- Java多執行緒之守護執行緒實戰Java執行緒
- 多執行緒_鎖執行緒
- 執行緒鎖(四)執行緒