對Java執行緒同步的認識
synchronized關鍵字
Java以提供關鍵字synchronized的形式,以防止多執行緒時資源衝突提供了內建支援。當任務要執行被synchronized關鍵字保護的程式碼片段時,它將檢查鎖是否可用,然後獲取鎖,執行程式碼,釋放鎖。
所有物件都自動含有單一的鎖(監視器)。當在物件上呼叫其任意synchronized方法時,此物件都被加鎖。
synchronized鎖住的是括號裡的物件,而不是程式碼。對於非static的synchronized方法,鎖的物件其實就是this。
注:
- synchronized關鍵字也可以修飾靜態方法,此時如果呼叫該靜態方法,將會鎖住整個類(類鎖,見下面
synchronized(xxx.class)
synchronized methods(){}
與synchronized(this){}
之間本質上是一樣的,只是synchronized methods(){}
便於閱讀理解,而synchronized(this){}
可以更精確的控制衝突限制訪問區域,有時候表現更高效率。- synchronized關鍵字是不能繼承的,也就是派生的子類如果也有同步的要求需再次使用synchronized關鍵字。
synchronized方法
synchronized methods(){
//方法體
}
由於java的每個物件都有一個內建鎖,當用此關鍵字修飾方法時,內建鎖會保護整個方法。在呼叫該方法前,需要獲得內建鎖,否則就處於阻塞狀態。
synchronized程式碼塊
synchronized(syncObject) {
/允許訪問控制的程式碼
}
syncObject為互斥量(上鎖的目標),一般為類例項或類
當一個執行緒訪問object的一個synchronized(this)
同步程式碼塊時,它就獲得了這個object的物件鎖。此時其它執行緒對該object物件所有同步程式碼部分的訪問都被暫時阻塞。
同步程式碼塊synchronized(this)與synchronized(xx.class)的區別
有兩種型別的同步鎖,一種是物件鎖(Object lock),一種是類鎖(Class lock)。
物件鎖與一個具體的物件例項相關聯,每個物件例項只能有一個鎖。當一個執行緒進入一個例項方法或者synchronized(Object o){}
塊時物件鎖被啟用。
一個類鎖與一個具體的類相關聯,一個類只能有一個鎖。當執行緒進入靜態方法或者synchronized(X.class) {}
塊時類鎖被啟用。也就是說類鎖會鎖定所有該類的例項。
舉個栗子(見於CSDN某評論區):
- 假設蘋果是一個類,裡面有方法吃蘋果,有例項蘋果1,
synchronized(蘋果1){吃蘋果}
時(物件鎖),此時有一個人吃蘋果1,但只能一個一個的吃; - 再例項化一個 例項蘋果2,而且此時有兩個人a、b分別吃 蘋果1 、蘋果2 ,這時使用
synchronized(蘋果.class)
時(類鎖),當a吃蘋果(蘋果1)時b不能吃蘋果,反之亦然。
物件鎖與類鎖時相互獨立的,事實上對一個類來說,可能在某一執行緒擁有它的類鎖的同時,另一個執行緒擁有它的例項鎖。
volatile關鍵字
首先我們得了解什麼原子性。
原子性:在單處理器系統(UniProcessor)中,能夠在單條指令中完成的操作稱為”原子操作”,因為中斷只能發生於指令之間。原子操作是不可分割的,在直到執行完畢之間不會被任何其它任務或事件中斷。因此把一個操作這種無法再次分隔和中斷的特性稱為原子性。
在對稱多處理器(Symmetric Multi-Processor)結構中就不同了,由於系統中有多個處理器在獨立地執行,即使能在單條指令中完成的操作也有可能受到干擾。
原子操作是不能被執行緒排程中斷的操作。意味著可以利用原子性來編寫無鎖的程式碼,這些程式碼不需要同步。
原子性可以應用於除long和double之外的所有基本型別的“簡單操作”,對於這些型別可以保證它們會被當作不可分的(原子)操作來操作記憶體。
定義long和double型別變數時可以使用volatile關鍵字,讓他們獲得(簡單賦值和返回操作的)原子性。
使用方式:
volatile 成員變數
原子操作確保了應用中的可視性,如果將一個域申明為volatile,那麼只要對這個域產生了寫操作,那麼所有的讀操作都可以看到這個修改。即便使用了本地快取也是如此,volatile域會被立即寫到主存中,而讀寫操作就發生在主存中。
- volatile關鍵字為域變數的訪問提供了一種免鎖機制
- 使用volatile修飾域相當於告訴虛擬機器該域可能會被其他執行緒更新
- 因此每次使用該域就要重新計算,而不是使用暫存器中的值
注意:
- 如果你不是專家級的程式設計師,不要依賴於原子性來編寫無鎖的程式碼。
- 依賴於原子性可能是不安全的,因此避免使用原子性代替同步。
- 使用volatile而不是synchronized的唯一安全情況是類中只有一個可變的域,而第一選擇和最安全的方式是採用synchronized關鍵字。
使用顯式的lock物件
Java SE5的java.uitl.concurrent類庫了有定義在java.util.concurrent.locks中的顯式的互斥機制。lock物件必須被顯式地建立,鎖定和釋放。因此,它與內建的鎖形式相比,程式碼缺乏優雅性。但在解決某些型別的問題時更加靈活。
使用方法如下:
Lock lock = new Reentrantlock();
lock.lock();
try{
//同步的程式碼段
}finally{
//釋放鎖
lock.unlock();
}
使用try catch是為了當某些異常丟擲了,你可以使用finally子句以維護在正常的狀態。
使用synchronized關鍵字時,需要的程式碼量更少,並且使用者錯誤出現的可能性更低。因此通常只有在解決特殊問題時,才會使用顯式lock的這種方式。例如獲取鎖一段時間。
sleep()方法與wait()方法
從所屬的類來看,
sleep()
方法屬於Thread中的,wait()
方法在Object中。從資源排程上看,
wait()
方法會釋放物件鎖(等待CPU),sleep()
方法不會(佔著CPU睡覺)。使用範圍:
wait()
,notify()
和notifyAll()
方法只能在同步控制方法或者同步控制塊裡面使用,而sleep可以在任何地方使用。
//在同步程式碼塊中呼叫wait()方法
synchronized(x){
x.notify();
//或者wait()
}