並行處理
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"); } };
說明:如果是JDK 8 的應用,可以使用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() 實例。
正例:在 JDK 7 之後,可以直接使用 API ThreadLocalRandom ,在 JDK 7 之前,可以做到每個
#線程一個實例。
12. 【推薦】透過雙重檢查鎖定(double - checked locking)( 在並發場景) 實現延遲初始化的優化問題隱患( 可參考 The " Double - Checked Locking is Broken " Declaration) ,建議問題解決方案中較為簡單一種( 適用於JDK 5 以上版本) ,將目標屬性宣告為 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 ); 如果是JDK 8,推薦骨使用LongAdder 物件,比AtomicLong性能更好( 減少樂觀鎖的重試次數) 。
14. 【參考】 HashMap 在容量不夠進行resize 時由於高並發可能出現死鏈,導致CPU 飆升,在
開發過程中註意規避此風險。
15. 【參考】 ThreadLocal 無法解決共享物件的更新問題, ThreadLocal 物件建議使用 static修飾。這個變數是針對一個線程內所有操作共有的,所以設定為靜態變量,所有此類實例共享此靜態變量,也就是說在類別第一次被使用時裝載,只分配一塊存儲空間,所有此類的物件( 只要是這個執行緒內定義的) 都可以操控這個變數。