天天看點

并發技巧清單不變模式JAVA的并發控制方式隐藏比較深的資料競争

如何盡量保證線程安全

  • 可變狀态是至關重要的。

    所有并發通路都可以歸結為如何協調對并發狀态的通路,可變狀态越少,越容易確定線程安全性。

  • 盡量将域聲明為final類型,除非需要它們是可變的。
  • 不可變對象一定是線程安全的。
    不可變對象能極大地降低并發程式設計的複雜性。它們更為簡單且安全,可以任意共享而無須使用加鎖或保護性複制等機制。
               
  • 封裝有助于管理複雜性。 在編寫線程安全的程式時,雖然可以将所有資料都儲存在全局變量,但為什麼要這樣做? 将資料封裝在對象中,更易于維護不變性條件:将同步機制封裝在對象中,更易于遵循同步政策。
  • 用鎖保護每個可變變量。
  • 當保護同一個不變性條件中的所有變量時,要使用同一個鎖。
  • 在執行複合操作期間,要持有鎖。
  • 如果從多個線程中通路同一個可變變量時沒有同步機制,那麼程式會可能出問題。
  • 不要故作聰明地推斷不需要使用同步。
  • 在設計過程中考慮線程安全,或者在文檔中明确地指出塔不是線程安全的。
  • 将同步政策文檔化。
  • 使用ThreadLocal儲存狀态變量

避免死鎖

1.對于資源的加鎖時間必須足夠短,也就是必要時進行鎖

2.通路資源過程中的鎖需要按照一緻的順序進行擷取,否則需要提升出一個更大的鎖來確定資源的擷取

3.盡量通過封裝的形式,避免将鎖暴露給外部,進而造成不必要的資源死鎖

4.多數情況下,死鎖是由于擷取鎖的順序錯誤鎖導緻的。

5.避免一個線程同時擷取多個鎖。

6.避免一個線程在鎖内同時占用多個資源,盡量保持一個鎖隻占用一個資源。

7.嘗試使用定時鎖,使用lock.tryLock(timeout)來替代使用内部鎖機制。 8.

原子性

原子(atom)本意是“不能被進一步分割的最小粒子”,而原子操作(atomic operation)意為"不可被中斷的一個或一系列操作" 。 在多處理器上實作原子操作就變得有點複雜。

對象類型

  • 對象位址原子讀寫,線程安全。
  • 并發讀不可變狀态,線程安全。
  • 并發讀寫可變狀态,非線程安全。

基本類型

  • int,char數值讀寫,線程安全。
  • long,double高低位,非線程安全。
  • i++ 等組合操作,非線程安全。

可見性

final

  • 初始化final字段確定可見性

volatile

  • 讀寫volatile字段確定可見性

synchronized

  • 同步塊内讀寫字段確定可見性

happen before

  • 遵守happen before次序可見性

可排序性

Happen Before法則

  • 程式次序法則 如果A一定在B之前發生,happen before
  • 螢幕法則 對一個螢幕的解鎖一定發生在後續對同一個螢幕加鎖之前
  • Volatile變量法則 寫volatile變量一定發生在後續對它讀之前
  • 線程啟動法則 Thread.start一定發生線上程中的動作之前
  • 線程終結法則 線程中的任何動作一定發生在以下操作的動作之前。
    其它線程檢測到這個線程已經終止,從Thread.join調用成功傳回,Thread.isAlive()傳回false
               
  • 中斷法則 一個線程調用另一個線程的interrupt一定發生在另一線程發現中斷之前
  • 終結法則 一個對象的構造函數結束一定發生在對象的finalizer之前
  • 傳遞性 A發生在B之前,B發生在C之前,A一定發生在C之前

如果多個線程通路同一個可變的狀态變量時,沒有使用合适的同步,那麼程式就會出現錯誤。有三種方式可以修複這個問題:

  1. 不線上程之間共享該狀态變量
  2. 将狀态變量修改為不可變的變量
  3. 在通路狀态變量時使用同步

什麼是線程安全性

線上程安全性的定義中,最核心的概念就是正确性。 當多個線程通路某個類時,不管運作時環境采用何種排程方式或者這些線程将如何交替執行,并且在主調用代碼中不需要任何額外的同步或協同,這個類都能表現出正确的行為,那麼就稱這個類是線程安全的。

線上程安全類中封裝了必要的同步機制,是以用戶端無需進一步采取同步措施。

無狀态對象一定是線程安全的。

熟練使用線程安全類

要保持狀态的一緻性,就需要在單個原子操作中更新所有相關的狀态變量。

并非所有的資料都需要鎖的保護,隻有被多個線程同時通路的可變資料才需要通過鎖來保護。

對非原子的64位操作,如long,double類型的變量,在多線程下存在這樣的共享變量時,請把變量定義成volatile

加鎖機制既可以確定可見性又可以確定原子性 ,volatile變量隻可以確定可見性。

當通路共享的可變資料時,通常需要同步,一種避免使用同步方式就是不共享資料。

滿足不可變對象的條件:

  • 對象建立以後其狀态不能修改
  • 對象的所有域都是final類型
  • 對象是正确建立的(在對象建立的期間,this引用沒有逸出)

final域能確定初始化過程中的安全性

不變模式

在并行程式開發過程中,同步操作似乎是必不可少的。當多線程對同一個對象進行讀寫操作時,為了保證資料對象對一緻性 和正确性,有必要對對象使用同步。故而同步操作對性能有相當對消耗。為了盡可能的去除這些同步操作,提高并行程式的 性能,可以使用一種不可變的對象,依靠對象的不變性,可以確定其在沒有使用同步操作的多線程環境下依然始終保持 内部狀态的一緻性和正确性。

不變模式天生就是多線程友好的,它的核心思想是,一個對象一旦被建立,則它的内部狀态将永遠不會發生改變。是以,沒有 一個線程可以修改其内部狀态和資料。

不變模式使用場景介紹

  • 當對象被建立後,其内部狀态和資料不再發生任何變化
  • 對象需要被共享、被多線程頻繁通路

JAVA的并發控制方式

  • 内部鎖
  • 重入鎖
  • 讀寫鎖
  • ThreadLocal變量
  • 信号量

隐藏比較深的資料競争

競争情況描述 線程T1 線程T2
編譯器将這個表達式擴充成temp=x ,和 x=temp+1 x+=1 x+=2
下标i和j相同時可能會出現資料競争 a[i]+=1 a[j]+=1
指針q和p指向同一個目标時可能會出現資料競争 *q+=2 *p+=1
foo函數可能使用參數對一個共享變量進行修改 foo(1) foo(2)
即使在指令集,硬體還是會将【edi】對更新操作擴充成獨立對對讀操作和寫操作改 add [edi],1 add [edi] ,2

歡迎工作一到五年的Java工程師朋友們加入Java架構開發:468947140

點選連結加入群聊【Java-BATJ企業級資深架構】:https://jq.qq.com/?_wv=1027&k=5zMN6JB

本群提供免費的學習指導 架構資料 以及免費的解答

不懂得問題都可以在本群提出來 之後還會有職業生涯規劃以及面試指導