1_基礎(chǔ)知識_chapter02_線程安全性_1_線程安全性

  • 對象的狀態(tài)

    (1) 對象的狀態(tài)是指 類的實例或靜態(tài)變量

    (2) 也包括其他依賴對象的域

    例如某個HashMap的狀態(tài)不僅存儲在HashMap對象本身,還存儲在許多Map.Entry對象中

  • Java的同步機制

    (1) synchronized

    (2) volatile

    (3) 顯示鎖

    (4) 原子變量: package java.util.concurrent.atomic

  • 將線程不安全的類修改為線程安全的類的方法

    (1) 不在線程之間共享狀態(tài)變量

    (2) 將狀態(tài)變量修改為不可變變量

    (3) 在訪問狀態(tài)變量時使用同步

  • 程序狀態(tài)的封裝性越好, 越容易實現(xiàn)程序的線程安全性

  • 線程安全性

    (1) 定義

    當多個線程訪問某個類時,不論運行時環(huán)境采用何種調(diào)度方式, 或者線程如何調(diào)度執(zhí)行, 在主調(diào)代碼中不需要任何額外的同步, 這個類總能表現(xiàn)出正確的行為

    (2) 無狀態(tài)的類一定是線程安全的

    即,類中沒有任何靜態(tài)變量和實例變量,類只提供方法,所有變量都是局部變量。

    沒有狀態(tài)變量,自然也就沒有線程同步的問題

  • 競態(tài)條件

    (1) 定義

    由于執(zhí)行時序的不同,造成不正確的結(jié)果稱為競態(tài)條件

    (2) 常見的競態(tài)條件情形

    先檢查后執(zhí)行

    示例

      if (!flag) {
          ....
      }
    

    讀取-修改-寫入

    示例

      count += 1
    

    (因為count+=1這一條語句事實上包含了讀取-修改-寫入三個步驟,不是原子操作)

    (3) 當在無狀態(tài)的類中添加一個狀態(tài)變量時,如果該狀態(tài)完全由線程安全的對象來管理,那么這個類一定是線程安全的

    實際情況中,應(yīng)盡可能使用線程安全的類作為狀態(tài)變量,例如java.util.concurrent.atomic中的類

    示例

    線程不安全

      @NotThreadSafe
      public class UnsafeCountingFactorizer extends GenericServlet implements Servlet {
    
          private long count = 0;
    
          public long getCount() {
    
              return count;
          }
    
          public void service(ServletRequest req, ServletResponse resp) {
    
              ...
    
              ++count;
    
              ...
          }
      }
    

    線程安全

      @ThreadSafe
      public class CountingFactorizer extends GenericServlet implements Servlet {
    
          private final AtomicLong count = new AtomicLong(0);
    
          public long getCount() { return count.get(); }
    
          public void service(ServletRequest req, ServletResponse resp) {
    
              ...
    
              count.incrementAndGet();
    
              encodeIntoResponse(resp, factors);
          }
      }
    

    其中,count.get()和count.incrementAndGet();都是AtomicLong類提供的實例方法

    (3) 狀態(tài)變量有多個時,對每個變量的更新都使用原子操作,結(jié)果未必是原子操作

    正確的做法是:

    當更新某一個狀態(tài)變量時, 如果各個狀態(tài)變量之間不是彼此獨立的,那么需要在同一個原子操作中, 對其他變量同時進行更新

  • 內(nèi)置鎖與synchronized

    (1) 每個Java對象都可以用作一個實現(xiàn)同步的鎖,稱為內(nèi)置鎖(Intrinsic Lock)或監(jiān)控器鎖(Monitor Lock),用作synchronized(...)

    (2) 在synchronized塊中,無論程序正常運行結(jié)束,還是拋出異常,都會在退出同步代碼塊時自動釋放鎖

    (3) 重入

    如果一個線程試圖獲得一個已經(jīng)由它自己持有的鎖, 那么這個請求一定會成功, 不會引發(fā)死鎖

    例如

    遞歸函數(shù) 如果加上了synchronized, 每次調(diào)用自己時不會被阻塞

    2° __子類覆蓋了父類的synchronized方法, 且在方法中又顯示調(diào)用父類的方法時(super.xxx()),不會被阻塞

一種實現(xiàn)原理是, 為每個鎖關(guān)聯(lián)一個計數(shù)值和一個所有者線程,如果請求鎖時發(fā)現(xiàn)占有鎖的是所有者線程本身,那么只會增加計數(shù)值,不會阻塞
  • (1) 某個線程獲得對象的鎖以后, 只能阻止其他線程獲得同一個鎖, 不能阻止其他線程獲取其他的鎖

    因此, 每個共享的和可變的變量都應(yīng)該只由一個鎖來保護

    (2) 一種常見的加鎖約定

    將所有的可變、共享的狀態(tài)變量都封裝在對象內(nèi)部, 并通過對象本身(this)的內(nèi)置鎖對所有訪問可變狀態(tài)的代碼路徑進行同步

  • 同步代碼塊的大小

    (1) 理論上, 可以將所有方法都盡可能加上synchronized,但是一來濫用會導(dǎo)致性能問題,二來每個方法都是原子操作,不代表整體就是原子操作

    (2) 同步代碼塊的合理大小取決于三個因素

    安全性: 必須達到

    簡單性: 同步的部分設(shè)計不能過于復(fù)雜

    性能: 同步代碼塊范圍過大會導(dǎo)致性能問題

    (3) 安全性必須達到, 簡單性和性能常常是矛盾的

    當實現(xiàn)某個同步策略時,一定不能盲目的為了性能而犧牲簡單性;

    當執(zhí)行時間較長的計算和IO操作時,一定不要繼續(xù)持有鎖。

    (4) 一個設(shè)計合理的示例

      @ThreadSafe
      public class CachedFactorizer extends GenericServlet implements Servlet {
    
          @GuardedBy("this")
          private BigInteger lastNumber;
    
          @GuardedBy("this")
          private BigInteger[] lastFactors;
    
          @GuardedBy("this")
          private long hits;
    
          @GuardedBy("this")
          private long cacheHits;
    
          public synchronized long getHits() {
    
              return hits;
          }
    
          public synchronized double getCacheHitRatio() {
    
              return (double) cacheHits / (double) hits;
          }
    
          public void service(ServletRequest req, ServletResponse resp) {
    
              BigInteger i = extractFromRequest(req);
              BigInteger[] factors = null;
    
              synchronized (this) {
                  ++hits;
                  if (i.equals(lastNumber)) {
                      ++cacheHits;
                      factors = lastFactors.clone();
                  }
              }
    
              if (factors == null) {
    
                  factors = factor(i);
                  
                  synchronized (this) {
                      lastNumber = i;
                      lastFactors = factors.clone();
                  }
              }
    
              encodeIntoResponse(resp, factors);
          }
    
          private void encodeIntoResponse(ServletResponse resp, BigInteger[] factors) {
          }
    
          private BigInteger extractFromRequest(ServletRequest req) {
    
              return new BigInteger("7");
          }
    
          private BigInteger[] factor(BigInteger i) {
    
              return new BigInteger[]{i};
          }
      }
    

    這個示例首先滿足了安全性; 其次將同步代碼塊的范圍盡可能縮小, 滿足了性能要求; 再次沒有將同步代碼塊拆的很零碎,滿足了簡單性要求。所以是一個設(shè)計良好的類。

    對單個變量實現(xiàn)原子操作來說, 原子變量(AtomicXXX)很有效;但是由于多個原子操作的疊加未必是一個原子操作,所以當狀態(tài)變量很多時, 仍有考慮所有狀態(tài)變量的同步更新訪問問題,即便它們都使用了原子變量。

    在這個示例中,對hits和cacheHits這兩個狀態(tài)變量的更新和訪問使用了synchronized機制,沒有必要引入原子變量,增大設(shè)計的復(fù)雜程度

  • 總結(jié)

    (1) 無狀態(tài)的類一定是線程安全的

    (2) 當在無狀態(tài)的類中添加一個狀態(tài)變量時,如果該狀態(tài)完全由線程安全的對象來管理,那么這個類一定是線程安全的

    例如只添加一個狀態(tài)變量時,添加一個原子變量類型AtomicXXX就很合適

    (3) 當更新某一個狀態(tài)變量時, 如果各個狀態(tài)變量之間不是彼此獨立的,那么需要在同一個原子操作中, 對其他變量同時進行更新

    一種常見的約定是: 將所有的可變、共享的狀態(tài)變量都封裝在對象內(nèi)部, 并通過對象本身(this)的內(nèi)置鎖對所有訪問可變狀態(tài)的代碼路徑進行同步

    (4) 同步代碼塊的大小要合理,必須滿足安全性,且在簡單性性能方面權(quán)衡合適

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 線程安全性:當多個線程訪問某個類時,這個類始終表現(xiàn)出正確的行為,則這個類是線程安全的。編寫線程安全的代碼,核心在于...
    Rockie_h閱讀 377評論 0 0
  • 第三章 Java內(nèi)存模型 3.1 Java內(nèi)存模型的基礎(chǔ) 通信在共享內(nèi)存的模型里,通過寫-讀內(nèi)存中的公共狀態(tài)進行隱...
    澤毛閱讀 4,500評論 2 21
  • Java8張圖 11、字符串不變性 12、equals()方法、hashCode()方法的區(qū)別 13、...
    Miley_MOJIE閱讀 3,895評論 0 11
  • Java多線程學(xué)習(xí) [-] 一擴展javalangThread類 二實現(xiàn)javalangRunnable接口 三T...
    影馳閱讀 3,107評論 1 18
  • 本文主要講了java中多線程的使用方法、線程同步、線程數(shù)據(jù)傳遞、線程狀態(tài)及相應(yīng)的一些線程函數(shù)用法、概述等。 首先講...
    李欣陽閱讀 2,594評論 1 15

友情鏈接更多精彩內(nèi)容