1. 程式人生 > >java的四種狀態 無鎖狀態 偏向鎖狀態 輕量級鎖狀態 重量級鎖狀態

java的四種狀態 無鎖狀態 偏向鎖狀態 輕量級鎖狀態 重量級鎖狀態

一:java多執行緒互斥,和java多執行緒引入偏向鎖和輕量級鎖的原因?--->synchronized的重量級別的鎖,就是線上程執行到該程式碼塊的時候,讓程式的執行級別從使用者態切換到核心態,把所有的執行緒掛起,讓cpu通過作業系統指令,去排程多執行緒之間,誰執行程式碼塊,誰進入阻塞狀態。這樣會頻繁出現程式執行狀態的切換,執行緒的掛起和喚醒,這樣就會大量消耗資源,程式執行的效率低下。為了提高效率,jvm的開發人員,引入了偏向鎖,和輕量級鎖,儘量讓多執行緒訪問公共資源的時候,不進行程式執行狀態的切換。--->jvm規範中可以看到synchronized在jvm裡實現原理,jvm基於進入和退出Monitor物件來實現方法同步和程式碼塊同的。在程式碼同步的開始位置織入monitorenter,在結束同步的位置(正常結束和異常結束處)織入monitorexit指令實現。執行緒執行到monitorenter處,將會獲取鎖物件對應的monitor的所有權,即嘗試獲得物件的鎖。(任意物件都有一個monitor與之關聯,當且一個monitor被持有後,他處於鎖定狀態)--->java的多執行緒安全是基於lock機制實現的,而lock的效能往往不如人意。原因是,monitorenter與monitorexit這兩個控制多執行緒同步的bytecode原語,是jvm依賴作業系統互斥(mutex)來實現的。--->互斥是一種會導致執行緒掛起,並在較短時間內又需要重新排程回原執行緒的,較為消耗資源的操作。--->為了優化java的Lock機制,從java6開始引入輕量級鎖的概念。輕量級鎖本意是為了減少多執行緒進入互斥的機率,並不是要替代互斥。它利用了cpu原語Compare-And-Swap(cas,彙編指令CMPXCHG),嘗試進入互斥前,進行補救。二:為什麼要自旋或者自適應自旋?--->前面我們討論互斥同步的時候,提到了互斥同步對效能最大的影響是阻塞的實現,掛起執行緒和恢復執行緒的操作都需要轉入核心態中完成,這些操作給系統的併發效能 帶來了很大的壓力。同時,虛擬機器的開發團隊也注意到在許多應用上,共享資料的鎖定狀態只會持續很短的一段時間,為了這段時間去掛起和恢復執行緒並不值得。如 果物理機器有一個以上的處理器,能讓兩個或以上的執行緒同時並行執行,我們就可以讓後面請求鎖的那個執行緒“稍等一會”,但不放棄處理器的執行時間,看看持有 鎖的執行緒是否很快就會釋放鎖。為了讓執行緒等待,我們只須讓執行緒執行一個忙迴圈(自旋),這項技術就是所謂的自旋鎖。 --->自旋鎖在JDK 1.4.2中就已經引入,只不過預設是關閉的,可以使用-XX:+UseSpinning引數來開啟,在JDK 1.6中就已經改為預設開啟了。自旋等待不能代替阻塞,且先不說對處理器數量的要求,自旋等待本身雖然避免了執行緒切換的開銷,但它是要佔用處理器時間的, 所以如果鎖被佔用的時間很短,自旋等待的效果就會非常好,反之如果鎖被佔用的時間很長,那麼自旋的執行緒只會白白消耗處理器資源,而不會做任何有用的工作, 反而會帶來效能的浪費。因此自旋等待的時間必須要有一定的限度,如果自旋超過了限定的次數仍然沒有成功獲得鎖,就應當使用傳統的方式去掛起執行緒了。自旋次 數的預設值是10次,使用者可以使用引數-XX:PreBlockSpin來更改。 --->在JDK 1.6中引入了自適應的自旋鎖。自適應意味著自旋的時間不再固定了,而是由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態來決定。如果在同一個鎖物件 上,自旋等待剛剛成功獲得過鎖,並且持有鎖的執行緒正在執行中,那麼虛擬機器就會認為這次自旋也很有可能再次成功,進而它將允許自旋等待持續相對更長的時間, 比如100個迴圈。另一方面,如果對於某個鎖,自旋很少成功獲得過,那在以後要獲取這個鎖時將可能省略掉自旋過程,以避免浪費處理器資源。有了自適應自 旋,隨著程式執行和效能監控資訊的不斷完善,虛擬機器對程式鎖的狀況預測就會越來越準確,虛擬機器就會變得越來越“聰明”了。 三:鎖削除--->鎖削除是指虛擬機器即時編譯器在執行時,對一些程式碼上要求同步,但是被檢測到不可能存在共享資料競爭的鎖進行削除。鎖削除的主要判定依據來源於逃逸分析的數 據支援(第11章已經講解過逃逸分析技術),如果判斷到一段程式碼中,在堆上的所有資料都不會逃逸出去被其他執行緒訪問到,那就可以把它們當作棧上資料對待, 認為它們是執行緒私有的,同步加鎖自然就無須進行。 --->也許讀者會有疑問,變數是否逃逸,對於虛擬機器來說需要使用資料流分析來確定,但是程式設計師自己應該是很清楚的,怎麼會在明知道不存在資料爭用的 情況下要求同步呢?答案是有許多同步措施並不是程式設計師自己加入的,同步的程式碼在Java程式中的普遍程度也許超過了大部分讀者的想象。比如:(只是說明概念,但實際情況並不一定如例子)線上程安全的環境中使用stringBuffer進行字串拼加。則會在java檔案編譯的時候,進行鎖銷除。四:鎖粗化--->原則上,我們在編寫程式碼的時候,總是推薦將同步塊的作用範圍限制得儘量小——只在共享資料的實際作用域中才進行同步,這樣是為了使得需要同步的運算元量儘可能變小,如果存在鎖競爭,那等待鎖的執行緒也能儘快地拿到鎖。--->大部分情況下,上面的原則都是正確的,但是如果一系列的連續操作都對同一個物件反覆加鎖和解鎖,甚至加鎖操作是出現在迴圈體中的,那即使沒有執行緒競爭,頻繁地進行互斥同步操作也會導致不必要的效能損耗。--->如果虛擬機器探測到有這樣一串零碎的操作都對同一個物件加鎖,將會把加鎖同步的範圍擴充套件(鎖粗化)到整個操作序列的外部。五:偏向鎖,輕量級鎖,重量級鎖對比
優點缺點適用場景
偏向鎖加鎖和解鎖不需要額外的消耗,和執行非同步方法相比僅存在納秒級的差距如果執行緒間存在鎖競爭,會帶來額外的鎖撤銷的消耗適用於只有一個執行緒訪問同步塊場景
輕量級鎖競爭的執行緒不會阻塞,提高了程式的響應速度如果始終得不到索競爭的執行緒,使用自旋會消耗CPU追求響應速度,同步塊執行速度非常快
重量級鎖執行緒競爭不使用自旋,不會消耗CPU執行緒阻塞,響應時間緩慢追求吞吐量,同步塊執行速度較慢
物件頭的儲存內容(monitor)
長度內容說明
32/64bitMark Word儲存物件的hashcode或鎖資訊
32/64bit類物件的地址儲存到物件型別資料的指標
32/64bitArray length陣列的長度(如果當前物件是陣列)
其中Mark Word儲存內容如下六:鎖的狀態--->鎖一共有四種狀態(由低到高的次序):無鎖狀態,偏向鎖狀態,輕量級鎖狀態,重量級鎖狀態--->鎖的等級只可以升級,不可以降級。這種鎖升級卻不能降級的策略,目的是為了提高獲得鎖和釋放鎖的效率。七:偏向鎖

大多數情況下,鎖不僅不存在多執行緒競爭,而且總是由同一執行緒多次獲得,為了讓執行緒獲得鎖的代價更低而引入了偏向鎖。當一個執行緒訪問同步塊並獲取鎖時,會在物件頭和棧幀中的鎖記錄裡儲存鎖偏向的執行緒ID,以後該執行緒在進入和退出同步塊時不需要進行CAS操作來加鎖和解鎖,只需簡單地測試一下物件頭的Mark Word裡是否儲存著指向當前執行緒的偏向鎖。如果測試成功,表示執行緒已經獲得了鎖。如果測試失敗,則需要再測試一下Mark Word中偏向鎖的標識是否設定成1(表示當前是偏向鎖):如果沒有設定,則使用CAS競爭鎖;如果設定了,則嘗試使用CAS將物件頭的偏向鎖指向當前執行緒。

--->a執行緒獲得鎖,會在a執行緒的的棧幀裡建立lockRecord,在lockRecord裡和鎖物件的MarkWord裡儲存執行緒a的執行緒id.以後該執行緒的進入,就不需要cas操作,只需要判斷是否是當前執行緒。--->a執行緒獲取鎖,不會釋放鎖。直到b執行緒也要競爭該鎖時,a執行緒才會釋放鎖。--->偏向鎖的釋放,需要等待全域性安全點(在這個時間點上沒有正在執行的位元組碼),它會首先暫停擁有偏向鎖的執行緒,然後檢查持有偏向鎖的執行緒是否還活著,如果執行緒不處於活動狀態,則將鎖物件的MarkWord設定成無鎖狀態。如果執行緒仍然活著,擁有偏向鎖的棧會被執行。棧幀中的lockRecord和物件頭的MarkWord要麼重新偏向其他執行緒,要麼恢復到無鎖,或者標記物件不適合作為偏向鎖。最後喚醒暫停的執行緒。--->關閉偏向鎖,通過jvm的引數-XX:UseBiasedLocking=false,則預設會進入輕量級鎖。偏向鎖升級:一個物件剛開始例項化的時候,沒有任何執行緒來訪問它的時候。它是可偏向的,意味著,它現在認為只可能有一個執行緒來訪問它,所以當第一個執行緒來訪問它的時候,它會偏向這個執行緒,此時,物件持有偏向鎖。偏向第一個執行緒,這個執行緒在修改物件頭MarkWord成為偏向鎖的時候使用CAS操作,並將物件頭中的ThreadID改成自己的ID,之後再次訪問這個物件時,只需要對比ID,不需要再使用CAS在進行操作。一旦有第二個執行緒訪問這個物件,因為偏向鎖不會主動釋放,所以第二個執行緒可以看到物件時偏向狀態,這時表明在這個物件上已經存在競爭了,檢查原來持有該物件鎖的執行緒是否依然存活,如果掛了,則可以將物件變為無鎖狀態,然後重新偏向新的執行緒,如果原來的執行緒依然存活,則馬上執行那個執行緒的操作棧,檢查該物件的使用情況,如果仍然需要持有偏向鎖,則偏向鎖升級為輕量級鎖(偏向鎖就是這個時候升級為輕量級鎖的)。如果不存在使用了,則可以將物件回覆成無鎖狀態,然後重新偏向。八:輕量級鎖輕量級鎖加鎖:執行緒在執行同步塊之前,JVM會先在當前執行緒的棧楨中建立用於儲存鎖記錄的空間,並將物件頭中的Mark Word複製到鎖記錄中,官方稱為Displaced Mark Word。然後執行緒嘗試使用CAS將物件頭中的Mark Word替換為指向鎖記錄的指標。如果成功,當前執行緒獲得鎖,如果失敗,表示其他執行緒競爭鎖,當前執行緒便嘗試使用自旋來獲取鎖。輕量級鎖解鎖:輕量級解鎖時,會使用原子的CAS操作將Displaced Mark Word替換回到物件頭,如果成功,則表示沒有競爭發生。如果失敗,表示當前鎖存在競爭,鎖就會膨脹成重量級鎖。--->a執行緒獲得鎖,會在a執行緒的棧幀裡建立lock record(鎖記錄變數),讓lock record的指標指向鎖物件的物件頭中的mark word.再讓mark word 指向lock record.這就是獲取了鎖。--->輕量級鎖,b執行緒在鎖競爭時,發現鎖已經被a執行緒佔用,則b執行緒不進入核心態,讓b執行緒自旋,執行空迴圈,等待a執行緒釋放鎖。如果,完成自旋策略還是發現a執行緒沒有釋放鎖,或者讓c執行緒佔用了。則b執行緒試圖將輕量級鎖升級為重量級鎖。
輕量級鎖認為競爭存在,但是競爭的程度很輕,一般兩個執行緒對於同一個鎖的操作都會錯開,或者說稍微等待一下(自旋),另一個執行緒就會釋放鎖。 但是當自旋超過一定的次數,或者一個執行緒在持有鎖,一個在自旋,又有第三個來訪時,輕量級鎖膨脹為重量級鎖,重量級鎖使除了擁有鎖的執行緒以外的執行緒都阻塞,防止CPU空轉。九:重量級鎖:就是讓爭搶鎖的執行緒從使用者態轉換成核心態。讓cpu藉助作業系統進行執行緒協調。