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

Java Concurrency #9 - Composing Objects

shutterstock_198004562
  • 目前為止我們談了很多底層的基礎如執緒行安全同步,但每次寫程式都要分析每段記憶體存取是否安全很累,此篇的重點就在於如何較輕鬆建構執行緒安全的物件
    • 設計一個執行緒安全的類別的流程如下:
      • 找出所有構成物件 state 的變數
      • 找出那些 state 受到什麼樣的不變量(Invariant)限制
      • 建立一個管理同步存取的機制
    • 物件都是由成員變數所組成的,如果它們全部都是原始型態(primitive type),那麼就構成了整個物件的 state
      • 如下方程式碼所示:
          public final class Counter {
              private long value = 0;
              public synchronized long getValue() {
                  return value;
              }
              public synchronized long increment() {
                  if(value == Long.MAX_SIZE) {
                      throw new IllegalStateException("counter overflow")
                  }
                  return ++counter;
              }
          }
        • 上面是典型的 Java Monitor Pattern
        • Counter 這個類別只有一個 field,其物件的 state 就由它獨自組成
        • 在這裡的 invariant 就是我遞增之後值要加一,不論在任何執行緒看到的結果都是如此
        • 透過 synchronized 管控取值與遞增,可以確保此類別所建立的物件皆為執行緒安全
    • 如果物件的成員變數包含了參考到其它物件的話,那物件的 state 就要連那些被參考到的物件的 state 也考量在內
      • 舉例來說 LinkedList 所參考到的所有 Node 的 state 會是構成 LinkedList 本身的 state 的要素之一
      • 同步的政策決定了如何與其 state 互動而不破壞 invariant,這通常是思考 Immutability, Thread Confinement, Locking 要透過什麼樣的組合來確保執行緒安全
    • 執行緒安全就是在並行存取的時候還能保持不變量
      • 物件與變數都有其值域,這個值域愈小,複雜度愈低,可以的話 final 就好
      • 比方說上面的 Counter,正常來說 long 的值域是從 Long.MIN_VALUE 到 Long.MAX_VALUE,但在 Counter 中限制負值的存在,因此它的 invariant 就是其 state value 必須為正整數
      • 此外,有的操作是有所謂的 post-condition 的,以上面的 Counter 當例子,如果現在的 value 是 17,那下一個正確的值只能是 18
        • pre-condition 是在執行某條指令前必須成立的條件,比方說 setHour(int hour) 這個方法,它的 pre-condition 就是傳入的參數 hour 必須是介於 0-23 之間
        • post-condition 是在執行某條指令必須成立的條件,例子已經看過了
      • state 的值域是受限的,或是有 post-condition 的存在,處理執行緒安全的時候會需要更多的同步封裝
        • 如果 state 在某些值的情況下是錯誤的,那就要封裝這個 state,不讓 client 端不小心設到錯誤的值
        • 如果操作本身不是 atomic,就必須以同步的方式處理之
        • 如果沒有以上的限制,那就可以相對少封裝且效能也比較好
    • 有些操作是依賴於 state 的,我們稱作 State-dependent operations
      • 比方說無法對一個空的佇列呼叫 remove,因為佇列必須在非空的狀態下才能從中移除元素
      • 在單執行緒的時候,如果方法的前提不成立,那就只會執行失敗
      • 在多執行緒的時候,事情就沒這麼單純了,因為另一個執行緒可能會因為現在的執行緒做了某項操作(e.g. 塞值),而使得前提條件成立了。因此在並行的程式中,等待某個條件成立才操作是可行的
      • 內建的 waitnotify 正是這樣的機制然而:
        • 它們跟內建鎖(Intrinsic lock)綁得很緊,使得實作上不太簡單
        • 使用 Synchronizers 像是 BlockingQueue, Semaphore 會單純許多
        • 以上兩類型的實作都會在後續的篇章中提及
[JavaScript] WebSocket (ft. React Context)
Java Concurrency #8 - Safe Publification

相關文章

 

評論

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

Captcha 圖像