本文主要資源來自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):