1. 程式人生 > 實用技巧 >Java併發程式設計實踐——讀書筆記(一)

Java併發程式設計實踐——讀書筆記(一)

Java併發程式設計實踐——讀書筆記(一)

《Java Concurrency in Practice》第一部分的閱讀總結

關鍵字:併發、鎖、執行緒安全、共享物件、併發容器、訊號量

目錄

第一章 介紹

1.1 併發的簡短歷史

1.2 執行緒的優點

​ 1.3 執行緒的風險

1.4 執行緒無處不在

第二章 執行緒安全

​ 2.1 什麼是執行緒安全性

​ 2.2 原子性

​ 2.3 鎖

​ 2.4 用鎖來保護狀態

2.5 活躍度與效能

​ 第三章 共享物件

​ 3.1 可見性

​ 3.2 釋出和逸出

​ 3.3 執行緒封閉

​ 3.4 不可變性

3.5 安全釋出

​ 第四章 組合物件

4.1 設計執行緒安全的類

4.2 例項限制

4.3 委託執行緒安全

4.4 向已有的執行緒安全類新增功能

4.5 同步策略的文件化

​ 第五章 構建塊

​ 5.1 同步容器

​ 5.2 併發容器

​ 5.3 阻塞佇列

5.4 阻塞和可中斷的方法

​ 5.5 Synchronizer

5.6 為計算結果建立高效、可伸縮的快取記憶體

第一章

  • 執行緒的風險

    • 競爭場景(race condition)

      多個執行緒併發地對共享資料進行操作

    • 活躍度失敗(liveness failure)(程式無法繼續執行或退出)

      如果安全意味著”什麼壞事都沒有發生“,那麼活躍度關注的則是”好事最終發生了“

    • 效能危險(performance)

      ...,執行緒仍然會給執行時帶來一定程度的開銷。上下文切換(Context switches) ,...,這在多個執行緒組成的應用程式中是很頻繁的,並且帶來巨大的系統開銷。...,CPU的時間會花費在對執行緒的排程上而不是在執行上。

第二章

  • 執行緒的安全性

    正確性意味著一個類與它的規約保持一致。良好的規約定義了用於強制物件狀態的不變約束(invariants)以及描述操作影響的後驗條件(postcoditions)

    一個類是執行緒安全的,是指在被多個執行緒訪問時,類可以持續進行正確的行為

  • 原子性

    假設有操作A和B,如果從執行A的執行緒的角度看,當其他執行緒執行B的時候,耀目B全部執行完成,要麼一點都沒執行,這樣A和B互為原子操作

    • Java.uitl.concurrent.atomic包中包括了原子變數類(atomic variable),這些類實現了數字和物件引用的原子轉換(該物件的操作都是原子的)

      • AtomicLong(基本型別對應的原子物件)
      • AtomicReference<V>(一些物件引用對應的原子物件)
    • 複合操作——”讀-改-寫“和”檢查再執行“

      • 這是兩類常見的看似原子,但是卻不是原子的操作。讀改寫對應賦值語句,檢查再執行對應條件判斷
    • 內部鎖

      • synchronized(鎖物件的引用,鎖程式碼塊)
      • 方法級別的synchronized,鎖物件是方法的呼叫者。靜態方法的synchronized,鎖物件是對應的呼叫者物件的class物件
      • 內部鎖是一種互斥鎖,至多隻有一個執行緒可以擁有鎖,但是內部鎖是可重入的。
    • 重進入(Reentrancy)

      執行緒在試圖獲得它自己佔優的鎖的時候,如果請求成功,那麼該鎖是可重入的。

  • 鎖對狀態的保護

    對於每個可被多個執行緒訪問的可變狀態變數,如果所有訪問它的執行緒在執行時都佔有同一個鎖,這種情況下,我們稱這個變數是由這個鎖保護的

    • 內部鎖只能確保一件事,一個執行緒獲得物件的鎖之後,將阻塞其他執行緒獲得這個鎖
    • Vector 和Hashtable
      • 通過使用物件的內部鎖(synchronized關鍵字)來封裝所有的可變狀態

    對於每一個涉及多個變數的不變約束,需要同一個鎖保護其他所有的變數

第三章

  • 可見性(defined)

    在沒有同步的情況下,編譯器、處理器,執行時安排操作的執行順序可能完全出入意料。在沒有進行適當同步的多執行緒程式中,嘗試推斷那些”必然"發生在記憶體中的動作,你總會判斷錯誤。

    可見性保證了,記憶體中的值是已定義的(也就是能夠判斷出來的),可能聽著還是有些抽象。舉個例子,多個執行緒對一個64位的long long變數進行值的修改,那麼可能執行緒A正在修改變數的高32位地址,而執行緒B在修改變數的低32位地址,這樣最後的產生的值就是undefined,也就是無法判斷的。

    • 可見性比並發的要求弱, 意味著資料可能過期

    • Volatile

      • 輕量級的同步機制
      • 只保證了對應變數引用的記憶體是“可見的”,不保證資料同步 。
        • 從主存讀,並且寫入記憶體。(不存在cpu暫存器中,效能損失)
    • 鎖不僅僅是關於同步與互斥的,也是關於記憶體可見的。為了保證所有執行緒都能夠看到共享的、可變變數的最新值,讀取和寫入執行緒必須使用公共的鎖進行同步

  • 物件的釋出與逸出(publishing & escape)

    • 釋出:使它能夠被當前範圍之外的程式碼所使用
      • 將物件的引用儲存到公共靜態域中
      • 從非私有方法中返回引用
      • 釋出一個物件,同樣也釋出了該物件所有非私有域所引用的物件
    • 逸出:未經計劃的釋出
  • 執行緒封閉

    • 區域性變數

    • ThreadLocal類

      ThreadLocal允許你將每個執行緒與持有數值的物件關聯在一起。ThreadLocal提供了get和set訪問器,為每個使用它的執行緒維護一份單獨的拷貝。所以get總是返回由當前執行執行緒通過set設定的最新值。

      本質是全域性設定一個容器,通過判斷currentThread,然後存入容器中來實現。

    • 單一執行緒對Volatile變數進行寫操作。

  • 不可變性(不可變物件)

    • 不可變物件:只有訪問器方法,可變狀態被封裝
    • 不可變物件永遠是執行緒安全的(可變物件的final引用,不叫不可變

第五章

  • 同步容器

    • Vector和Hashtable

    • Collections.synchronizedXxx(Xxx xxx)方法建立的同系列,如:

      List t = Collections.synchronizedList(new ArrayList()) ;
      
    • 不要輕易使用/隱藏使用同步容器的toString方法,由於被synchronized關鍵字修飾,序列,所以可能非常耗時。

  • 併發容器

    同步容器通過對容器的所有狀態進行序列訪問,從而實現了它們的執行緒安全。這樣做的代價是削弱了併發性。併發容器就是在此基礎上進行了設計,犧牲了部分的同步性來換取併發效能。

    • Queue
      • BlockingQueue
        • LinkedBlockingQueue
        • ArrayBlockingQueue
        • PriorityBlockingQueue
        • SynchronousQueue
      • ConcurrentLinkedQueue
      • Deque
        • ArrayDeque
        • LinkedBlockingDeque
    • Map
      • ConcurrentHashMap—— Hashtable/ synchronizedMap
      • ConcurrentSkipListMap ——synchronizedSortedMap
    • Set
      • ConcurrentSkipListSet —— synchronizedSortedSet
    • List
      • CopyOnWriteArrayList —— synchronizedList
        • 讀不同步,寫同步
  • 阻塞佇列(Blocking Queue) & 生產者-消費者模式

    • 阻塞佇列天然支援生產者-消費者模式
    • Deque與竊取模式
  • Synchronizer(同步器)

    • latch(閉鎖)
      • 同步開始與結束
    • semaphore(訊號量)
      • 實現阻塞佇列
    • barrier(關卡)
      • cycle