選單
GSS 技術部落格
在這個園地裡我們將從技術、專案管理、客戶對談面和大家分享我們多年的經驗,希望大家不管是喜歡或是有意見,都可以回饋給我們,讓我們有機會和大家對話並一起成長!
若有任何問題請來信:gss_crm@gss.com.tw
5 分鐘閱讀時間 (1070 個字)

Java Concurrency #7 - Immutability

shutterstock_198004562
  • 前面幾篇提到的 Atomicity, Visibility 都是同步時要處理的問題

    • 如果不處理的話可能會造成:

      • 看到過期的 state(Stale state)
      • state 的值更新不正常(Race Condition)
        • read-then-write
        • check-then-act
    • 而解法也提過了:

      • 透過 lock - syncrhonized blocks
      • 透過 volatile - 確保 visibility
      • 透過 ThreadLocal 斷開 shared 的特性
    • 但實際上有另外一個方法可以確保執行緒安全,那就是 Immutability

      • 如果 state 不會變,那就不用考慮以上提到的情形
      • Immutable object 的 state 在建構子初始化的時候就決定了,因此不會再異動
      • 此外,Immutable object 被當作參數傳遞時也不用擔心內容會被修改
      • 什麼是 Immutable object,在這可以簡單定義如下:
        • 它的 state 在建立後就不可修改
        • 所有的成員變數都是 final
        • 它有正確被建立(this 沒逃出來)
      • 以下是一個範例:
          public final class ThreeStooges {
              private final Set<String> stooges = new HashSet<String>();
              public ThreeStooges() {
                  stooges.add("Moe");
                  stooges.add("Larry");
                  stooges.add("Curly");
              }
              public boolean isStooge(String name) {
                  return stooges.contains(name);
              }
          }
        • ThreeStooges 是一個 immutable object,它所有的成員變數都是 final,雖然 stooges 本身參考不能變動,但裡面存的 String 是可以增加的,所以這部份是 mutable
        • 承上,雖然如此,此類別並無公開任何方法能夠異動 Set 的內容,因此實際上這個類別還是 immutable
        • 最後一點,建構子並未讓 this 參考逃出
      • 你可能會認為,state 一直都是會異動的, immutable object 一點用都沒有,不過實際上並非如此:
        • 物件本身 immutable指向 immutable 物件的參考是不一樣的
        • 用 immutable object 當作 state 的物件,可以參考到新的實例就沒問題了,未被參考到的物件就會進入可回收的狀態
        • 有的人可能會覺得這是很大的效能議題,但其實沒這麼嚴重,跟要用各種 lock 或防禦性複本(defensive copy)比起來,建立一個 instance 所造成的負擔小多了
    • Final fields

      • final 加在成員變數前,會使得變數的值無法再變動
        • primitive type 無法異動
        • reference type 無法改變指向的物件,但被參考的物件如果是 mutable 還是可以異動
      • 這在 Java memory model 有特殊的句法在(後面會提)
        • 因為有 final field,使得初始化物件在沒有 lock 的情況也能保持執行緒安全
        • 當一個類別有愈多的 final field,處理同步的複雜度就會降低許多
        • 如同變數非必要就訂為 private 一樣,如果沒有變動的需求,state 也盡量訂為 final
    • 透過 volatile + immutable 來達到 Thread Safe

      • 前面我們看過用 AtomicReference 來存放階乘的運算結果以及最後的數字,但因為是兩個相關的操作,因此如果沒有用 lock,就不會是執行緒安全的,就算用 volatile 也是一樣

      • 在這我們多了一項武器,就是 immutable object,當你有兩個以上相關聯要維護的 state 時,不妨把這些 state 包在一個 immutable holder class,如下面的範例如示:

          class OneValueCache {
              private final BigInteger lastNumber;
              private final BigInteger[] lastFactors;
        
              public OneValueCache(BigInteger i, BigInteger[] factors) {
                  lastNumber = i;
                  lastFactors = Arrays.copyOf(factors, factors.length);
              }
        
              public BigInteger[] getFactors(BigInteger i) {
                  if (lastNumber == null || !lastNumber.equals(i))
                      return null;
                  else
                      return Arrays.copyOf(lastFactors, lastFactors.length);
              }
          }
        • 這是上面提到的 holder class,持有兩個 final 的 state
        • 特別留意 Arrays.copyOf,如果沒有使用這個方法,OneValueCache 就不會是 immutable
          • 這是因為傳入建構子的參考型態傳的是參考本身,並非物件實例,如果不是把內容 copy 一份由 final 的陣列來指向,那之後陣列的內容就有可能從外部被異動
          • getFactors 也是同樣的原理,傳出去的不能是指向原始陣列的參考,必須 copy 一份才能保持 immutable
      • 定義好了 OneValueCache 之後,我們可以再加上 volatile,如下所示:

          public class VolatileCachedFactorizer implements Servlet {
              private volatile OneValueCache cache = new OneValueCache(null, null);
        
              public void service(ServletRequest req, ServletResponse resp) {
                  BigInteger i = extractFromRequest(req);
                  BigInteger[] factors = cache.getFactors(i);
                  if (factors == null) {
                      factors = factor(i);
                      cache = new OneValueCache(i, factors);
                  }
                  encodeIntoResponse(resp, factors);
              }
          }
        • 原本 OneValueCache 就已經是個 immutable class,再加上 volatile 會使得 cache 這個 state 的所有異動可被其他的執行緒看到
        • 承上,跟 cache 相關的操作都只有一次(直接回傳,或計算後回傳),因此執行緒間不會互相干擾到 VolatileCachedFactorizer 的 state
Java Concurrency #8 - Safe Publification
Java Concurrency #6 - Thread Confinement

相關文章

 

評論

尚無評論
已經注冊了? 這裡登入
Guest
2024/05/06, 週一

Captcha 圖像