java 多線程注意事項

轉自:https://blog.csdn.net/kkgbn/article/details/56279659

java 多線程注意事項

一,線程池的概念

線程池的做用:

線程池做用就是限制系統中執行線程的數量。
     根據系統的環境狀況,能夠自動或手動設置線程數量,達到運行的最佳效果;少了浪費了系統資源,多了形成系統擁擠效率不高。用線程池控制線程數量,其餘線程排隊等候。一個任務執行完畢,再從隊列的中取最前面的任務開始執行。若隊列中沒有等待進程,線程池的這一資源處於等待。當一個新任務須要運行時,若是線程池中有等待的工做線程,就能夠開始運行了;不然進入等待隊列。

爲何要用線程池:

1.減小了建立和銷燬線程的次數,每一個工做線程均可以被重複利用,可執行多個任務。
2.能夠根據系統的承受能力,調整線程池中工做線線程的數目,防止由於消耗過多的內存,而把服務器累趴下(每一個線程須要大約1MB內存,線程開的越多,消耗的內存也就越大,最後死機)。
Java裏面線程池的頂級接口是Executor,可是嚴格意義上講Executor並非一個線程池,而只是一個執行線程的工具。真正的線程池接口是ExecutorService。


二,線程資源必須經過線程池提供,不容許在應用中自行顯式建立線程

說明:使用線程池的好處是減小在建立和銷燬線程上所花的時間以及系統資源的開銷,解決資源不足的問題。
若是不使用線程池,有可能形成系統建立大量同類線程而致使消耗完內存或者「過分切換」的問題。


三,SimpleDateFormat 是線程不安全的類,通常不要定義爲static變量,若是定義爲static,必須加鎖,或者使用DateUtils工具類。 

正例:注意線程安全,使用DateUtils。亦推薦以下處理:
private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
說明:若是是JDK8的應用,可使用Instant代替Date,LocalDateTime代替Calendar,DateTimeFormatter代替Simpledateformatter,
官方給出的解釋:simple beautiful strong immutable thread-safe。


四,高併發時,同步調用應該去考量鎖的性能損耗。

能用無鎖數據結構,就不要用鎖;能鎖區塊,就不要鎖整個方法體;能用對象鎖,就不要用類鎖。


五,對多個資源、數據庫表、對象同時加鎖時,須要保持一致的加鎖順序,不然可能會形成死鎖。 

說明:線程一須要對錶A、B、C依次所有加鎖後才能夠進行更新操做,
那麼線程二的加鎖順序也必須是A、B、C,不然可能出現死鎖。


六,併發修改同一記錄時,

避免更新丟失,要麼在應用層加鎖,要麼在緩存加鎖,要麼在數據庫層使用樂觀鎖,使用version做爲更新依據。 說明:若是每次訪問衝突機率小於20%,推薦使用樂觀鎖,不然使用悲觀鎖。樂觀鎖的重試次數不得小於3次。


七,多線程並行處理定時任務時,

Timer運行多個TimeTask時,只要其中之一沒有捕獲拋出的異常,其它任務便會自動終止運行,使用ScheduledExecutorService則沒有這個問題。


八,使用CountDownLatch進行異步轉同步操做,

每一個線程退出前必須調用countDown方法,線程執行代碼注意catch異常,確保countDown方法能夠執行,
避免主線程沒法執行至countDown方法,直到超時才返回結果。 說明:注意,子線程拋出異常堆棧,不能在主線程try-catch到。


九,避免Random實例被多線程使用,雖然共享該實例是線程安全的,但會因競爭同一seed 致使的性能降低。

說明:Random實例包括java.util.Random 的實例或者 Math.random()實例。 
正例:在JDK7以後,能夠直接使用API ThreadLocalRandom,在 JDK7以前,能夠作到每一個線程一個實例。


十,經過雙重檢查鎖(double-checked locking)(在併發場景)實現延遲初始化的優化問題隱患

(可參考 The "Double-Checked Locking is Broken" Declaration),推薦問題解決方案中較爲簡單一種(適用於JDK5及以上版本),將目標屬性聲明爲 volatile型。
 反例:
class Foo {
private Helper helper = null;
public Helper getHelper() {
if (helper == null) synchronized(this) {
if (helper == null)
helper = new Helper();
}
     return helper;
}
// other functions and members...
}


十一,volatile解決多線程內存不可見問題。

對於一寫多讀,是能夠解決變量同步問題,可是若是多寫,一樣沒法解決線程安全問題。
若是是count++操做,使用以下類實現:AtomicInteger count = new AtomicInteger(); count.addAndGet(1); 
若是是JDK8,推薦使用LongAdder對象,比AtomicLong性能更好(減小樂觀鎖的重試次數)。




十二,HashMap在容量不夠進行resize時因爲高併發可能出現死鏈,致使CPU飆升,在開發過程當中注意規避此風險。



十三,ThreadLocal沒法解決共享對象的更新問題,

ThreadLocal對象建議使用static修飾。這個變量是針對一個線程內全部操做共有的,因此設置爲靜態變量, 全部此類實例共享此靜態變量 ,也就是說在類第一次被使用時裝載,只分配一塊存儲空間,全部此類的對象(只要是這個線程內定義的)均可以操控這個變量。
相關文章
相關標籤/搜索