標籤:seed executor atomic 應用 tis rand 版本 更新問題 工具
轉自:56279659java 多線程注意事項2017年02月21日 12:42:42閱讀數:1753一,線程池的概念線程池的作用:線程池作用就是限制系統中執行線程的數量。
根據系統的環境情況,可以自動或手動設定線程數量,達到啟動並執行最佳效果;少了浪費了系統資源,多了造成系統擁擠效率不高。用線程池控制線程數量,其他線程排隊等候。一個任務執行完畢,再從隊列的中取最前面的任務開始執行。若隊列中沒有等待進程,線程池的這一資源處於等待。當一個新任務需要運行時,如果線程池中有等待的背景工作執行緒,就可以開始運行了;否則進入等待隊列。
為什麼要用線程池: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修飾。這個變數是針對一個線程內所有操作共有的,所以設定為靜態變數,
所有此類執行個體共用此靜態變數 ,也就是說在類第一次被使用時裝載,只分配一Block Storage空間,所有此類的對象(只要是這個線程內定義的)都可以操控這個變數。
java 多線程注意事項