Java 徹底弄明白synchronized的使用

多個線程訪問共享資源(臨界資源)的時候,會出現線程安全問題,安全問題大多數是可見性和原子性問題。但這樣說可能並不嚴謹,線程的安全性可能更在於他對錯誤性的定義,當多個線程訪問一個類時,如果可以需要考慮運行時環境的調度和交換,並且需要額外的同步保證結果正確,我們認為這個線程是有線程安全性問題的。下面我們討論一下可見性和原子性帶來的線程安全問題。

可見性的問題

例如執行多個線程執行a++,那麼多個線程就會被分配到不同的處理器上,每個處理器都從主存上覆制操作一份拷貝,處理完成後複製給主存。由於分配到了不同的處理器上,兩個線程的操作可能會互相覆蓋,這樣的結果就會和預想的又偏差。例如如下代碼:

Java 徹底弄明白synchronized的使用

上面的例子中,我們期望是1000,然而他卻有時候輸出997,998的結果,這是因為線程A執行這個操作之後,並沒有返回過去,導致線程B訪問的時候,訪問到的不是最新的數據。當然上面也有原子性的問題,不過分開討論。

原子性分解的問題

原子性就是不可分割的操作,在硬件層面上是指不被線程調度器中斷,也就是說一個操作要麼執行完,要麼不執行。上面的例子中a++就不是原子性操作,首先要讀取X的值,然後+1,最後寫入工作內存中。三個步驟任何一部打斷,都會影響最終的結果。但是a=1和return就是原子性操作了。

synchronized關鍵字

為了解決上述的原子性和可見性帶來的線程安全問題,Java提供了同步機制互斥鎖機制,這個機制保證了在同一時間內只有一個線程能訪問共享資源(臨界資源)。這個機制的保障來源於監視鎖Monitor,在Java中,每個對象都自帶監視鎖,當我們要訪問用synchronized修飾的方法或代碼塊的時候,都意味著進入這個方法或者代碼塊要加鎖,離開要放鎖。而且Synchronizd可以顯示的說明對哪個對象加鎖,如下例

Java 徹底弄明白synchronized的使用

對同步機制互斥鎖小結

1、每個對象有自己的監視鎖Monitor,這意味著多個線程訪問一個對象的Synchronizd方法或者代碼塊時,需要等其他線程放鎖才可訪問。相對的多個對象的監視鎖不存在互斥情況。

2、互斥機制只能保證Synchronizd代碼塊裡的代碼同步,而不再代碼塊裡的代碼不能得到保證。

3、子類重寫父類的Synchronizd方法不能保證同步。因為Synchronizd並不屬於方法定義的一部分。

4、SYnchronizd修飾靜態方法時,等同於給該類的所有對象都加了一把鎖。因為靜態方法屬於類,不屬於對象。

5,Synchronizd把類當監視鎖的話,效果等同4.

5、Synchronizd修飾的方法的監視鎖(Monitor)也是重入鎖,也就是說當一個線程獲得監視鎖之後,可以再次獲得該對象的鎖。例如,線程A調用一個對象的同步方法之後,可以調用該對象的另一個同步方法。


分享到:


相關文章: