用synchronized就一定線程安全嗎?


用synchronized對方法進行同步,還真不一定線程安全。

 

  這需要從synchronized的原理開始講起。synchronized關鍵字有下面三種用法:

  修飾實例方法:

    對當前實例加鎖,進入方法需要獲得當前實例的鎖修飾靜態方法:

    對當前類對象加鎖,進入靜態方法需要獲得當前類對象的鎖修飾代碼塊:

    對指定對象進行加鎖,進入代碼塊需要獲得指定對象的鎖

  那么上面三種方式有什么區別呢?

  這需要先理解下synchronized的底層語義。java中的同步是基於進入和退出管程(Moniter)對象來實現的,無論是顯式同步(有明確的monitorenter和monitorexit指令,即同步代碼塊)還是隱式同步(同步方法,方法調用指令讀取運行時常量池中的方法的ACC_SYNCHRONIZED標志來隱式實現的)。先看下基於對象實現的,需要了解下java對象。在JVM中,對象在內存中的區域分成三部分:對象頭,實例變量,填充數據。

  實例變量:存放類的屬性數據信息,包括父類的屬性信息,如果是數組的實例部分還包括數組的長度,這部分內存按4字節對齊。填充數據:由於虛擬機要求對象起始地址必須是8字節的整數倍。填充數據不是必須存在的,僅僅是為了字節對齊,這點了解即可。

  對象頭:是實現synchronized鎖對象的基礎。synchronized使用的鎖對象是存儲在Java對象頭里的,其主要結構是由Mark Word 和 Class Metadata Address 組成。

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

  synchronized的對象鎖,鎖標識位為10,其中指針指向的是monitor對象(也稱為管程或監視器鎖)的起始地址。每個對象都存在着一個 monitor 與之關聯,對象與其 monitor 之間的關系有存在多種實現方式,如monitor可以與對象一起創建銷毀或當線程試圖獲取對象鎖時自動生成,但當一個 monitor 被某個線程持有后,它便處於鎖定狀態。了解了對象頭,我們就可以知道為什么上面的代碼雖然都使用了synchronized修飾,但是還是有線程安全問題,因為靜態方法和實例方法鎖的對象是不一致的(Monitor不是同一個),所以導致最終沒有達到預期效果。


免責聲明!

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



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