您好,登錄后才能下訂單哦!
Sychronized的原理是什么,很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。
(1)進入時:monitorenter
每個對象有一個監視器鎖(monitor)。當monitor被占用時就會處于鎖定狀態,線程執行monitorenter指令時嘗試獲取monitor的所有權,過程如下:
1、如果monitor的進入數為0,則該線程進入monitor,然后將進入數設置為1,該線程即為monitor的所有者。
2、如果該線程已經占有該monitor,又重新進入,則進入monitor的進入數加1。
3、如果其他線程已經占用了monitor,則該線程進入阻塞狀態,直到monitor的進入數為0,再重新嘗試獲取monitor的所有權。
(2)退出時:monitorexit
執行monitorexit的線程必須是objectref所對應的monitor的所有者。
指令執行時,monitor的進入數減1,如果減1后進入數為0,那線程退出monitor,不再是這個monitor的所有者。其他被這個monitor阻塞的線程可以嘗試去獲取這個
monitor 的所有權。
通過這兩段描述,我們應該能很清楚的看出synchronized的實現原理,synchronized的語義底層是通過一個monitor的對象來完成。
其實wait/notify等方法也依賴于monitor對象,這就是為什么只有在同步的塊或者方法中才能調用wait/notify等方法,否則會拋出java.lang.IllegalMonitorStateException的異常的原因。
(3)當synchronized加在方法前時:
從反編譯的結果來看,方法的同步并沒有通過指令monitorenter和monitorexit來完成(其實也可以通過這兩條指令來實現)。
相對于普通方法,其常量池中多了ACC_SYNCHRONIZED標示符。
JVM就是根據該標示符來實現方法的同步的:
當方法被調用時,調用指令將會檢查方法的 ACC_SYNCHRONIZED 訪問標志是否被設置,如果設置了,執行線程將先獲取monitor,獲取成功之后才能執行方法體,方法執行完后再釋放monitor。在方法執行期間,其他任何線程都無法再獲得同一個monitor對象。 其實本質上沒有區別,只是方法的同步是一種隱式的方式來實現,無需通過字節碼來完成。
---------------------
以上轉自:https://blog.csdn.net/hbtj_1216/article/details/77773292
JVM對Sychronized的優化:
synchronized中的鎖一般分為重量鎖(對象鎖),自旋鎖,自適應自旋鎖,輕量鎖,偏向鎖
自旋鎖的應用場景:
線程的阻塞和喚醒需要CPU從用戶態轉為核心態,頻繁的阻塞和喚醒對CPU來說是一件負擔很重的工作,勢必會給系統的并發性能帶來很大的壓力。同時我們發現在許多應用上面,對象鎖的鎖狀態只會持續很短一段時間,為了這一段很短的時間頻繁地阻塞和喚醒線程是非常不值得的,所以引入自旋鎖。
若一個線程等待獲取鎖對象所持續的時間非常短,這時適合使用自旋鎖。所謂自旋鎖,就是等待鎖的線程并不進入阻塞狀態,而是執行一個無意義的循環。在循環結束后查看鎖是否已經被釋放,若已經釋放則直接進入執行狀態。因為長時間無意義循環也會大量浪費系統資源,因此自旋鎖適用于間隔時間短的加鎖場景。
自適應自旋鎖對自旋次數的調整:
JDK 1.6引入了更加聰明的自旋鎖,即自適應自旋鎖。所謂自適應就意味著自旋的次數不再是固定的,它是由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態來決定。線程如果自旋成功了,那么下次自旋的次數會更加多,因為虛擬機認為既然上次成功了,那么此次自旋也很有可能會再次成功,那么它就會允許自旋等待持續的次數更多。反之,如果對于某個鎖,很少有自旋能夠成功的,那么在以后要或者這個鎖的時候自旋的次數會減少甚至省略掉自旋過程,以免浪費處理器資源。
鎖消除
有些情況下,JVM檢測到不可能存在共享數據競爭,這時JVM會對這些同步鎖進行鎖消除。鎖消除的依據是逃逸分析的數據支持。
有時我們雖然沒有顯示使用鎖,但是我們在使用一些JDK的內置API時,如StringBuffer、Vector、HashTable等,它們的內部實現存在隱形的加鎖操作。比如StringBuffer的append()方法,Vector的add()方法。
在運行這段代碼時,JVM可以明顯檢測到變量vector沒有逃逸出方法vectorTest()之外,所以JVM可以大膽地將vector內部的加鎖操作消除。
鎖粗化
我們知道在使用同步鎖的時候,需要讓同步塊的作用范圍盡可能小—僅在共享數據的實際作用域中才進行同步,這樣做的目的是為了使需要同步的操作數量盡可能縮小,如果存在鎖競爭,那么等待鎖的線程也能盡快拿到鎖。
在大多數的情況下,上述觀點是正確的。但是如果一系列的連續加鎖解鎖操作,可能會導致不必要的性能損耗,所以引入鎖粗話的概念。
鎖粗化就是將多個連續的加鎖、解鎖操作連接在一起,擴展成一個范圍更大的鎖。如上面實例:vector每次add的時候都需要加鎖操作,JVM檢測到對同一個對象(vector)連續加鎖、解鎖操作,會合并一個更大范圍的加鎖、解鎖操作,即加鎖解鎖操作會移到for循環之外。
輕量鎖和偏向鎖:
適用于沒有線程競爭的情況。無法代替重量鎖
重量級鎖:
重量級鎖通過對象內部的監視器(monitor)實現,其中monitor的本質是依賴于底層操作系統的Mutex Lock實現,操作系統實現線程之間的切換需要從用戶態到內核態的切換,切換成本非常高。
(3)上面幾種鎖都是JVM自己內部實現,當我們執行synchronized同步塊的時候jvm會根據啟用的鎖和當前線程的爭用情況,決定如何執行同步操作;
在所有的鎖都啟用的情況下線程進入臨界區時會先去獲取偏向鎖,如果已經存在偏向鎖了,則會嘗試獲取輕量級鎖,如果以上兩種都失敗,則啟用自旋鎖,如果自旋也沒有獲取到鎖,則使用重量級鎖,沒有獲取到鎖的線程阻塞掛起,直到持有鎖的線程執行完同步塊喚醒他們;
偏向鎖--》輕量級鎖--》自旋鎖--》重量級鎖
看完上述內容是否對您有幫助呢?如果還想對相關知識有進一步的了解或閱讀更多相關文章,請關注億速云行業資訊頻道,感謝您對億速云的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。