1. 程式人生 > >對Java執行緒同步的認識

對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. 假設蘋果是一個類,裡面有方法吃蘋果,有例項蘋果1synchronized(蘋果1){吃蘋果}時(物件鎖),此時有一個人吃蘋果1,但只能一個一個的吃;
  2. 再例項化一個 例項蘋果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()
}