線程同步問題,volatile關鍵字和synchronized關鍵字

本文主要資源來自Effective Java這本書,相當於讀書筆記一樣,所屬權屬於該書作者。

1.同步訪問共享的可變數據

關鍵字synchronized可以保證在同一個時刻。只有一個線程可以執行某一個方法,或者某個代碼塊。


java語言規範保證讀或寫一個變量是原子的,除非這個變量的類型是long或者double

爲了在線程之間進行可靠的通信,也爲了互斥訪問,同步是必要的。

(本圖片主要來自書籍截圖。)

while(!done){

   i++;

}

會變成:

if(!done){

   while(true){

          i++;

}

}

解決方法:


2.volatile關鍵字,注意:這個關鍵字在使用n++,n=n+1等,是沒有用的,因爲不是原子性。

雖然volatile修飾符不執行互斥訪問,但它保證任何一個線程在讀取該域的時候將看到最近剛剛寫入的值。

public class Stop{
	private static volatile boolean stoopRequested;
	public static void main(String[] args){
		Thread backgroundThread = new Thread(new Runnable(){
			public void run(){
				int i =0;
				while(!stoopRequested)
					i++;
			}
		});
		backgroundThread.start();
		TimeUnit.SECONDS.sleep(1);
		stoopRequested =true;
	}
}
使用的時候要小心處理。



問題在於,增量操作符(++)不是原子的。它在nextSerialNumber域中執行兩項操作:首先它讀取值,然後寫一個新值

,然後寫回一個新值,相當於原來的值再加上1.如果第二個線程在第一個線程讀取舊值和寫回新值間讀取這個域,

第二個線程就會與第一個線程一起看到同一個值,並返回相同的序列號。這就是安全性失敗。

修正

一種方法是在它的聲明中增加synchronized修飾符。一旦這麼做,就可以且應該從nextSerialNumber中刪除volatile修飾符。爲了這個方法更可靠要有long代替int,或者在nextSerialNumber快要重疊時拋出異常。

通常,你應該再同步區域內做盡可能少的工作。


性能問題:

StringBuffer基本被StringBuilder代替,因爲StringBuffer實例幾乎總是被用於單個線程中,而他們執行的卻是內部同步。

當你不確定的時候,就不要同步你的類,而是應該建立文檔,註明它不是線程安全的。

當你在設計一個可變類的時候,要考慮一下它們是否應該自己完成同步操作。

4.線程安全性的文檔化

這寫文檔的時候,表明那個是同步線程的類,或者方法。方便處理。

5.慎用延遲初始化

就像大多數的優化一樣,對於延遲初始化,最好建議“除非絕對必要,否則就不要這麼做”。

它降低了初始化類或者創建實例的開銷,卻增加了訪問被延遲初始化的域的開銷。



如果處於性能的考慮而需要對實例域使用延遲加載初始化,就使用雙重檢查模式。



但是性能問題大大折扣,進行改進(注意是在jdk1.6之後,前面的jdk1.5):
















發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章