exp:併發處理 -規範
1.【強制】獲取單例物件需要保證執行緒安全,其中的方法也要保證執行緒安全。
說明:資源驅動類、工具類、單例工廠類都需要注意。
2.【強制】建立執行緒或執行緒池時請指定有意義的執行緒名稱,方便出錯時回溯。
正例:
public class TimerTaskThread extends Thread {public TimerTaskThread(){
super.setName(“TimerTaskThread”); …}
3.【強制】執行緒資源必須通過執行緒池提供,不允許在應用中自行顯式建立執行緒。
說明:使用執行緒池的好處是減少在建立和銷燬執行緒上所花的時間以及系統資源的開銷,解決資源不足的問題。如果不使用執行緒池,有可能造成系統建立大量同類執行緒而導致消耗完記憶體或者“過度切換”的問題。
4.【強制】執行緒池不允許使用 Executors 去建立,而是通過 ThreadPoolExecutor 的方式,這樣的處理方式讓寫的同學更加明確執行緒池的執行規則,規避資源耗盡的風險。說明:Executors 返回的執行緒池物件的弊端如下:
1)FixedThreadPool 和 SingleThreadPool:
允許的請求佇列長度為 Integer.MAX_VALUE,可能會堆積大量的請求,從而導致 OOM。
2)CachedThreadPool 和 ScheduledThreadPool:
允許的建立執行緒數量為 Integer.MAX_VALUE,可能會建立大量的執行緒,從而導致 OOM。
5.【強制】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 strongimmutable thread-safe
6.【強制】高併發時,同步呼叫應該去考量鎖的效能損耗。能用無鎖資料結構,就不要用鎖;能鎖區塊,就不要鎖整個方法體;能用物件鎖,就不要用類鎖。
7.【強制】對多個資源、資料庫表、物件同時加鎖時,需要保持一致的加鎖順序,否則可能會造成死鎖。
說明:執行緒一需要對錶 A、B、C 依次全部加鎖後才可以進行更新操作,那麼執行緒二的加鎖順序也必須是 A、B、C,否則可能出現死鎖。
8.【強制】併發修改同一記錄時,避免更新丟失,要麼在應用層加鎖,要麼在快取加鎖,要麼在資料庫層使用樂觀鎖,使用 version 作為更新依據。
說明:如果每次訪問衝突概率小於 20%,推薦使用樂觀鎖,否則使用悲觀鎖。樂觀鎖的重試次數不得小於 3 次。
9.【強制】多執行緒並行處理定時任務時,Timer 執行多個 TimeTask 時,只要其中之一沒有捕獲丟擲的異常,其它任務便會自動終止執行,使用 ScheduledExecutorService 則沒有這個問題。
10. 【推薦】使用 CountDownLatch 進行非同步轉同步操作,每個執行緒退出前必須呼叫 countDown方法,執行緒執行程式碼注意 catch 異常,確保 countDown 方法可以執行,避免主執行緒無法執行至 countDown 方法,直到超時才返回結果。
說明:注意,子執行緒丟擲異常堆疊,不能在主執行緒 try-catch 到。
11. 【推薦】避免 Random 例項被多執行緒使用,雖然共享該例項是執行緒安全的,但會因競爭同一seed 導致的效能下降。
說明:Random 例項包括 java.util.Random 的例項或者 Math.random()例項。
正例:在 JDK7 之後,可以直接使用 API ThreadLocalRandom,在 JDK7 之前,可以做到每個執行緒一個例項。
12.【推薦】通過雙重檢查鎖(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...
}
13 . 【參考】volatile 解決多執行緒記憶體不可見問題。對於一寫多讀,是可以解決變數同步問題,但是如果多寫,同樣無法解決執行緒安全問題。如果是 count++操作,使用如下類實現:AtomicInteger count = new AtomicInteger(); count.addAndGet(1); 如果是 JDK8,推薦使用 LongAdder 物件,比 AtomicLong 效能更好(減少樂觀鎖的重試次數)。
14 . 【參考】 HashMap 在容量不夠進行 resize 時由於高併發可能出現死鏈,導致 CPU 飆升,在開發過程中注意規避此風險。
15 . 【參考】ThreadLocal 無法解決共享物件的更新問題,ThreadLocal 物件建議使用 static修飾。這個變數是針對一個執行緒內所有操作共有的,所以設定為靜態變數,所有此類例項共享此靜態變數 ,也就是說在類第一次被使用時裝載,只分配一塊儲存空間,所有此類的物件(只要是這個執行緒內定義的)都可以操控這個變數。