synchronized介紹

一、在多線程併發編程中synchronized一直被稱爲重量級鎖,可是隨着Java se1.6 對synchronized進行了各類優化後,有些狀況下它就並不那麼重了。
synchronized實現同步的基礎:每一個對象均可以做爲鎖,具體分爲三種狀況:
對於普通同步方法,鎖的是本身實力對象
對於靜態同步方法,鎖的是當前類的Class對象
對於同步方法快,鎖的是synchronized括號裏配置的對象
JVM基於進入和退出Monitor對象來實現方法同步和代碼塊同步,可是二者實現細節不一樣:
代碼塊同步是使用monitorenter和monitorexit指令實現的。
同步方式使用的另外一種方法實現。
monitorenter指令是在編譯後插入到同步代碼塊的開始位置,而monitorexit是插入到方法結束處和異常處,JVM要保證每一個monitorenter和monitorexit成對出現。 任何一個對象都有一個monitor與之關聯,當且一個monitor被持有後,它將處於鎖定狀態。
線程執行到monitorenter指令時,將嘗試得到對象所對應的monitor的全部權,即嘗試得到對象的鎖。
 
二、synchronized用的鎖是存在Java對象頭裏的。若是對象是數組類型的話,則JVM用3個字節存儲對象頭,若是對象是非數組類型的話,則用2個字節存儲對象頭。
 
三、鎖升級與對比
Java SE1.6爲了減小得到鎖和釋放鎖帶來的性能消耗,引入了「偏向鎖」和「輕量級鎖」。
鎖一共有四種狀態,級別從低到高分別爲: 無鎖狀態、偏向鎖狀態、輕量級鎖狀態、重量級鎖狀態。這幾個狀態會隨着競爭狀況逐漸升級。鎖能夠升級可是不能降級,意味着偏向鎖到輕量級鎖後不能降級到偏向鎖,這種鎖升級不能降級的策略,目的是爲了提供得到鎖和釋放鎖的效率。
偏向鎖:
大多數狀況下,鎖不但不存在競爭,並且老是由同一線程屢次得到,爲了讓線程得到所得代價更低而引入了偏向鎖。
當一個線程訪問同步代碼塊並獲取鎖時,會在對象的頭和棧幀中的鎖記錄存儲鎖偏向的線程ID,之後該線程在進入和退出同步代碼塊時不須要進行CAS操做來加鎖和解鎖,只須要簡單的測試對象頭的Mark Word裏是否存儲着指向當前線程的偏向鎖,若是測試成功,表示線程已經得到鎖,若是是失敗,則須要在測試一下Mark Word中偏向鎖的標識是否設置成1(標識當前是偏向鎖):若是沒有設置,則使用CAS競爭鎖,若是設置了,則嘗試使用CAS將對象頭中的偏向鎖指向當前線程。
偏向鎖的撤銷:偏向鎖使用了一種等到競爭出現才釋放鎖的機制,因此當其餘線程嘗試競爭偏向鎖時,持有偏向鎖的線程纔會釋放鎖。
關閉偏向鎖:偏向鎖在Java6和7中是默認啓用的,可使用-XX:BiasedLockingStartupDelay=0選項開關閉啓動過程當中的延期。
輕量級鎖
輕量級鎖加鎖:
線程在執行同步代碼塊以前, JVM會先在當前線程的棧幀中建立用戶存儲鎖記錄的空間,並將對象頭的Mark Word複製到鎖記錄中,官方稱爲Displaced Mark Word。而後線程嘗試使用CAS將對象頭中Mark Word替換爲指向鎖記錄的指針。若是成功,當前線程得到鎖,若是失敗,表示其餘線程競爭鎖,當前線程變嘗試使用自旋鎖來得到鎖。
輕量級鎖解鎖:
輕量級鎖解鎖時, 會使用原子的CAS操做將Displaced Mark Word替換回到對象頭,若是成功,則表示沒有競爭發生,若是失敗,表示當前鎖存在競爭,鎖就會膨脹成重量級鎖。
 
由於自旋鎖會消耗CPU,爲了不無用的自旋,一旦鎖升級爲重量級鎖就不會再恢復到輕量級鎖狀態。當鎖處於這狀態時,其餘線程試圖得到鎖時,都會被阻塞,當持有鎖的線程釋放鎖以後會喚醒這些線程,被喚醒的線程就會進行新一輪的奪鎖。
優勢
缺點
使用場景
偏向鎖
加鎖和解鎖不須要額外的消耗,和執行非同步方法相比僅存在納秒級的差距
若是線程存在競爭的話會帶來額外的鎖撤銷消耗
適用於只有一個線程訪問同步塊的場景
輕量級鎖
競爭的線程不會阻塞,提升了線程的響應速度
若是始終得不到鎖競爭的線程,使用自旋會消耗CPU
追求響應時間,同步代碼塊執行速度很是快
重量級鎖
線程競爭不使用自旋,不會消耗CPU
線程阻塞,響應時間緩慢
追求吞吐量,同步代碼塊執行速度較長
相關文章
相關標籤/搜索