天天看點

JAVA程式設計規範之并發處理

1、【強制】擷取單例對象需要保證線程安全,其中的方法也要保證線程安全。

說明 : 資源驅動類、工具類、單例工廠類都需要注意。

2、【強制】建立線程或線程池時請指定有意義的線程名稱,友善出錯時回溯。

3、【強制】線程資源必須通過線程池提供,不允許在應用中自行顯式建立線程。

說明 : 使用線程池的好處是減少在建立和銷毀線程上所花的時間以及系統資源的開銷,解決資源不足的問題。如果不使用線程池,有可能造成系統建立大量同類線程而導緻消耗完記憶體或者“過度切換”的問題。

4、【強制】線程池不允許使用 Executors 去建立,而是通過 ThreadPoolExecutor 的方式,這樣的處理方式讓寫的同學更加明确線程池的運作規則,規避資源耗盡的風險。

說明 : Executors 傳回的線程池對象的弊端如下:

-FixedThreadPool 和 SingleThreadPool:

允許的請求隊列長度為 Integer.MAX_VALUE,可能會堆積大量的請求,進而導緻 OOM。

-CachedThreadPool 和 ScheduledThreadPool:

允許的建立線程數量為 Integer.MAX_VALUE,可能會建立大量的線程,進而導緻 OOM。

5、【強制】​

​SimpleDateFormat​

​​ 是線程不安全的類,一般不要定義為 ​

​static​

​​ 變量,如果定義為 ​

​static​

​​,必須加鎖,或者使用 ​

​DateUtils​

​ 工具類。

6、【強制】高并發時,同步調用應該去考量鎖的性能損耗。能用無鎖資料結構,就不要用鎖;能鎖區塊,就不要鎖整個方法體;能用對象鎖,就不要用類鎖。

7、【強制】對多個資源、資料庫表、對象同時加鎖時,需要保持一緻的加鎖順序,否則可能會造成死鎖。

說明 : 線程一需要對表 A、B、C 依次全部加鎖後才可以進行更新操作,那麼線程二的加鎖順序也必須是 A、B、C,否則可能出現死鎖。

8、【強制】并發修改同一記錄時,避免更新丢失,需要加鎖。要麼在應用層加鎖,要麼在緩存加鎖,要麼在資料庫層使用樂觀鎖,使用 version 作為更新依據。

說明 : 如果每次通路沖突機率小于 20%,推薦使用樂觀鎖,否則使用悲觀鎖。樂觀鎖的重試次數不得小于 3 次。

9、【強制】多線程并行處理定時任務時,​

​Timer​

​​ 運作多個 ​

​TimeTask​

​​ 時,隻要其中之一沒有捕獲抛出的異常,其它任務便會自動終止運作,使用 ​

​ScheduledExecutorService​

​ 則沒有這個問題。

10、【推薦】使用 ​

​CountDownLatch​

​​ 進行異步轉同步操作,每個線程退出前必須調用 ​

​countDown​

​​ 方法,線程執行代碼注意 catch 異常,確定 ​

​countDown​

​​ 方法可以執行,避免主線程無法執行至 ​

​await​

​ 方法,直到逾時才傳回結果。

說明 : 注意,子線程抛出異常堆棧,不能在主線程 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​

​ 型。

13、【參考】volatile 解決多線程記憶體不可見問題。對于一寫多讀,是可以解決變量同步問題,但是如果多寫,同樣無法解決線程安全問題。如果是 count++操作,使用如下類實作:​

​AtomicInteger count = new AtomicInteger(); count.addAndGet(1);​

​​ 如果是 JDK8,推薦使用 ​

​LongAdder​

​​ 對象,比 ​

​AtomicLong​

​ 性能更好(減少樂觀鎖的重試次數)。