02.16 阿里巴巴規範之併發處理,經常被併發搞懵的同學有福了!

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>() {/<dateformat>/<dateformat>

@ Override

protected DateFormat initialValue() {

return new SimpleDateFormat("yyyy-MM-dd");

}

};

說明:如果是 JDK 8 的應用,可以使用 Instant 代替 Date , LocalDateTime 代替 Calendar ,DateTimeFormatter 代替 SimpleDateFormat ,官方給出的解釋: simple beautiful strong immutable thread - safe 。

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

說明:儘可能使加鎖的代碼塊工作量儘可能的小,避免在鎖代碼塊中調用 RPC 方法。

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() 的方式。

正例:在 JDK 7 之後,可以直接使用 API ThreadLocalRandom ,而在 JDK 7 之前,需要編碼保證每個線程持有一個實例。

12. 【推薦】在併發場景下,通過雙重檢查鎖 (double - checked locking) 實現延遲初始化的優化問題隱患 ( 可參考 The " Double - Checked Locking is Broken " Declaration) ,推薦解決方案中較為簡單一種 ( 適用於 JDK 5 及以上版本 ) ,將目標屬性聲明為 volatile 型 。

反例:

class Singleton {

private Helper helper = null;

public Helper getHelper() {

if (helper == null) synchronized(this) {

if (helper == null)

helper = new Helper();

}

return helper;

}

// other methods and fields...

}

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

AtomicInteger count = new AtomicInteger(); count . addAndGet( 1 ); 如果是 JDK 8,推薦使用 LongAdder 對象,比 AtomicLong 性能更好 ( 減少樂觀鎖的重試次數 ) 。

14. 【參考】 HashMap 在容量不夠進行 resize 時由於高併發可能出現死鏈,導致 CPU 飆升,在開發過程中可以使用其它數據結構或加鎖來規避此風險。

15. 【參考】 ThreadLocal 無法解決共享對象的更新問題, ThreadLocal 對象建議使用 static修飾。這個變量是針對一個線程內所有操作共享的,所以設置為靜態變量,所有此類實例共享此靜態變量 ,也就是說在類第一次被使用時裝載,只分配一塊存儲空間,所有此類的對象 ( 只要是這個線程內定義的 ) 都可以操控這個變量。

有討論,才有進步,大家各抒己見,讓每位同學學到不一樣的!


分享到:


相關文章: