程序員必知的併發編程注意事項

獲取單例對象需要保證線程安全,其中的方法也要保證線程安全。

  • 單例對象會被多線程共享,因此要保證它是線程安全的,它其中的方法都要保證是線程安全的。
  • 工具類、資源驅動類、單例工廠類都要注意這個問題。

創建線程或線程池時請指定有意義的線程名稱,方便出錯時回溯。

線程資源必須通過線程池提供,不允許在應用中自行顯式創建線程。

  • 使用線程池的好處是減少在創建和銷燬線程上所花的時間以及系統資源的開銷,解決資源不足的問題。如果不使用線程池,有可能造成系統創建大量同類線程而導致消耗完內存或者 “過度切換”的問題。

線程池不允許使用 Executors 去創建,而是通過 ThreadPoolExecutor 的方式,這樣的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。

  • FixedThreadPool 和 SingleThreadPool: 允許的請求隊列長度爲 Integer.MAX_VALUE,可能會堆積大量的請求,從而導致 OOM。

  • CachedThreadPool 和 ScheduledThreadPool: 允許的創建線程數量爲 Integer.MAX_VALUE,可能會創建大量的線程,從而導致 OOM。

SimpleDateFormat 是線程不安全的類,一般不要定義爲static變量,如果定義爲 static,必須加鎖,或者使用 DateUtils 工具類。

  • 如果是 JDK8 的應用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar, DateTimeFormatter 代替 Simpledateformatter。

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

對多個資源、數據庫表、對象同時加鎖時,需要保持一致的加鎖順序,否則可能會造 成死鎖。

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

併發修改同一記錄時,避免更新丟失,需要加鎖。要麼在應用層加鎖,要麼在緩存加 鎖,要麼在數據庫層使用樂觀鎖,使用 version 作爲更新依據。

  • 如果每次訪問衝突概率小於 20%,推薦使用樂觀鎖,否則使用悲觀鎖。樂觀鎖的重試次 數不得小於 3 次。

多線程並行處理定時任務時,Timer 運行多個 TimerTask 時,只要其中之一沒有捕獲拋出的異常,其它任務便會自動終止運行,使用 ScheduledExecutorService 則沒有這個問題。

使用 CountDownLatch 進行異步轉同步操作,每個線程退出前必須調用 countDown 方法,線程執行代碼注意 catch 異常,確保 countDown 方法可以執行,避免主線程無法執行至 await 方法,直到超時才返回結果。

  • 請在try…finally語句裏執行countDown方法,與關閉資源類似。

避免 Random 實例被多線程使用,雖然共享該實例是線程安全的,但會因競爭同一 seed 導致的性能下降。

  • Random 實例包括 java.util.Random 的實例或者 Math.random()實例。
  • 在 JDK7 之後,可以直接使用 API ThreadLocalRandom。
  • 在 JDK7 之前,可以把Random放在ThreadLocal裏,只在本線程中使用。

通過雙重檢查鎖(double-checked locking)實現延遲初始化的優 化問題隱患,只要不是特別老的JDK版本(1.4以下),雙檢鎖是沒問題的。

volatile 解決多線程內存不可見問題。
對於一寫多讀,是可以解決變量同步問題,但是如果多寫,是無法解決線程安全問題的。

  • 如果是 count++操作,使用如下類實現:
    • AtomicInteger count = new AtomicInteger();
    • count.addAndGet(1);
  • 如果是 JDK8,推薦使用 LongAdder 對象,比 AtomicLong 性能更好(減少樂觀鎖的重試次數)。

HashMap 在容量不夠進行 resize 時由於高併發可能出現死鏈,導致 CPU 飆升,在開發過程中注意規避此風險。

  • 開發程序的時候要預估使用量,根據使用量來設置初始值。

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章