1. 程式人生 > >Java 徹底弄明白synchronized的使用

Java 徹底弄明白synchronized的使用

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

可見性的問題

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

private volatile static int a=0;
	private static void add(){
		a++;
	}
	
	public static void main(String[] args) throws InterruptedException {
		// TODO Auto-generated method stub
		for(int i=0;i<1000;i++){
			new Thread(new Runnable() {
				
				@Override
				public void run() {
					add();
				}
			}).start();
		}
		System.out.println(a);
			
	}

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

原子性分解的問題

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

synchronized關鍵字

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

對同步機制互斥鎖小結

1、每個物件有自己的監視鎖Monitor,這意味著多個執行緒訪問一個物件的Synchronizd方法或者程式碼塊時,需要等其他執行緒放鎖才可訪問。相對的多個物件的監視鎖不存在互斥情況。 2、互斥機制只能保證Synchronizd程式碼塊裡的程式碼同步,而不再程式碼塊裡的程式碼不能得到保證。 3、子類重寫父類的Synchronizd方法不能保證同步。因為Synchronizd並不屬於方法定義的一部分。 4、SYnchronizd修飾靜態方法時,等同於給該類的所有物件都加了一把鎖。因為靜態方法屬於類,不屬於物件。 5,Synchronizd把類當監視鎖的話,效果等同4. 5、Synchronizd修飾的方法的監視鎖(Monitor)也是重入鎖,也就是說當一個執行緒獲得監視鎖之後,可以再次獲得該物件的鎖。例如,執行緒A呼叫一個物件的同步方法之後,可以呼叫該物件的另一個同步方法。

參考資料:

Java併發開發實戰

蘭亭風雨:http://blog.csdn.net/ns_code/article/details/17199201

陽光日誌:http://blog.csdn.net/luoweifu/article/details/46613015

海子:http://www.cnblogs.com/dolphin0520/p/3923737.html