天天看點

并發程式設計之知識梳理 第一部分全文内容引用

Java 并發程式設計 第一部分

  • 全文内容
    • 并發
      • 是什麼?
        • 同步與異步
        • 并發與并行
        • 程序、線程、協程
          • 協程
        • Java中的并發關鍵字
          • synchronized
            • 表現形式
            • Java對象頭
          • volatile
            • CPU的術語定義
            • 使用優化
          • 原子操作
            • 處理器如何實作原子操作
            • Java如何實作
      • 為什麼?
      • 挑戰
        • 上下文切換
        • 死鎖
        • 資源限制
    • 多線程
      • JMM
        • 主記憶體與工作記憶體
        • 記憶體間互動操作
        • 重排序
          • 從源代碼到指令序列的重排序
          • as-if-serial語義
          • JMM解決方案
            • 記憶體屏障
        • happens-before
          • 為什麼?
            • JMM的設計示意圖
          • 是什麼?
            • volatile規則
            • start()規則
            • join()規則
        • volatile的記憶體語義
          • 是什麼?
          • 如何做到?
            • volatile重排序規則表
            • 基于保守政策的JMM記憶體屏障插入政策
            • 指令序列示意圖
        • 鎖的記憶體語義
          • 鎖的釋放-擷取建立的happens-before關系
          • 鎖記憶體語義的實作
        • final域的記憶體語義
        • 雙重檢查鎖定與延遲初始化
          • 雙重檢查鎖定的由來
          • 問題的根源
          • 基于volatile的解決方案
          • 基于類初始化的解決方案
          • 解決方案的比較
      • Java中的線程
        • 線程的狀态
          • 有哪些?
          • 變遷
        • Daemon線程
        • 優先級
        • 啟動和終止線程
          • 建立
          • 中斷
        • 線程間的通信
          • synchronized
            • 底層實作
            • JVM的優化
          • 等待/通知機制
          • Thread.join()的使用
          • ThreadLocal的使用
        • Lock接口
          • 我有synchronized沒有的
          • API
        • 隊列同步器(AQS)
          • AbstractQueuedSynchronizer
          • 實作分析
            • 同步隊列(FIFO)
            • 獨占式同步狀态擷取與釋放
            • 共享式同步狀态擷取與釋放
            • 獨占式逾時擷取同步狀态
        • 重入鎖(ReentrantLock)
        • 讀寫鎖(ReentrantReadWriteLock)
        • LockSupport工具
        • Condition接口
          • API
          • 實作分析
  • 引用

你好! 這是我對于Java 并發程式設計相關知識點的梳理與思考,希望對你能有所幫助;菜鳥萌新,問題多多,歡迎指出,謝謝!本文章以圖為主,如果文章中沒有圖檔,麻煩移步: 連結.

全文内容

并發程式設計之知識梳理 第一部分全文内容引用

并發

并發程式設計之知識梳理 第一部分全文内容引用

是什麼?

同步與異步

并發與并行

程序、線程、協程

  • 程序作為擁有資源的基本機關,線程作為排程和配置設定的基本機關。
  • 資源配置設定給程序,同一程序的所有線程共享該程序的所有資源。
  • 多程序的程式要比多線程的程式健壯(程序崩潰不影響其它程序),但在程序切換時,耗費資源較大,效率要差一些
  • 程序/線程之間的親緣性決定了同一個程序/線程隻在某個cpu上運作,避免因切換帶來的CPU的L1/L2 cache失效而造成性能損失
    并發程式設計之知識梳理 第一部分全文内容引用
    并發程式設計之知識梳理 第一部分全文内容引用
協程
  • 不被作業系統核心所管理,而完全是由程式所控制(也就是在使用者态執行)
  • 極高的執行效率:因為子程式切換不是線程切換,而是由程式自身控制,是以,沒有線程切換的開銷,和多線程比,線程數量越多,協程的性能優勢就越明顯;

Java中的并發關鍵字

synchronized
  • 由于Java中的線程是與作業系統的原生線程一一對應的,是以當阻塞一個線程時,需要從使用者态切換到核心态執行阻塞操作,這是很耗時的操作,而synchronized的使用就會導緻上下文切換

表現形式

  • 對于普通同步方法,鎖是目前執行個體對象。
  • 對于靜态同步方法,鎖是目前類的Class對象。
  • 對于同步方法塊,鎖是Synchonized括号裡配置的對象。

Java對象頭

并發程式設計之知識梳理 第一部分全文内容引用
volatile
并發程式設計之知識梳理 第一部分全文内容引用

CPU的術語定義

并發程式設計之知識梳理 第一部分全文内容引用
  • 為了提高處理速度,處理器不直接和記憶體進行通信
  • Lock字首指令會引起處理器緩存回寫到記憶體(鎖緩存/鎖總線)
  • 一個處理器的緩存回寫到記憶體會導緻其他處理器的緩存無效(緩存一緻性協定)

使用優化

  • 追加位元組
原子操作
并發程式設計之知識梳理 第一部分全文内容引用

處理器如何實作原子操作

并發程式設計之知識梳理 第一部分全文内容引用

Java如何實作

并發程式設計之知識梳理 第一部分全文内容引用

為什麼?

并發程式設計之知識梳理 第一部分全文内容引用
  • 對應用系統性能和吞吐量要求

挑戰

并發程式設計之知識梳理 第一部分全文内容引用

上下文切換

  • 在單CPU時代多線程程式設計是沒有太大意義的,并且線程間頻繁的上下文切換還會帶來額外開銷

死鎖

  • 互斥條件
  • 請求并持有條件
  • 不可剝奪條件
  • 環路等待條件

資源限制

多線程

JMM

主記憶體與工作記憶體

  • CPU、記憶體和 I/O 裝置 存在速度差異性問題
    并發程式設計之知識梳理 第一部分全文内容引用
    并發程式設計之知識梳理 第一部分全文内容引用
    并發程式設計之知識梳理 第一部分全文内容引用
    并發程式設計之知識梳理 第一部分全文内容引用
并發程式設計之知識梳理 第一部分全文内容引用

記憶體間互動操作

并發程式設計之知識梳理 第一部分全文内容引用

重排序

從源代碼到指令序列的重排序
并發程式設計之知識梳理 第一部分全文内容引用
as-if-serial語義
并發程式設計之知識梳理 第一部分全文内容引用
JMM解決方案
  • 對于處理器重排序,插入記憶體屏障指令

記憶體屏障

并發程式設計之知識梳理 第一部分全文内容引用

happens-before

如果一個操作執行的結果需要對另一個操作可見,那麼這兩個操作之間必須要存在happens- before關系

為什麼?

JMM的設計示意圖

并發程式設計之知識梳理 第一部分全文内容引用
并發程式設計之知識梳理 第一部分全文内容引用
是什麼?
并發程式設計之知識梳理 第一部分全文内容引用

volatile規則

并發程式設計之知識梳理 第一部分全文内容引用

start()規則

并發程式設計之知識梳理 第一部分全文内容引用

join()規則

并發程式設計之知識梳理 第一部分全文内容引用

volatile的記憶體語義

是什麼?
并發程式設計之知識梳理 第一部分全文内容引用
如何做到?

volatile重排序規則表

并發程式設計之知識梳理 第一部分全文内容引用

基于保守政策的JMM記憶體屏障插入政策

并發程式設計之知識梳理 第一部分全文内容引用

指令序列示意圖

volatile寫

并發程式設計之知識梳理 第一部分全文内容引用

volatile讀

并發程式設計之知識梳理 第一部分全文内容引用
  • 在實際執行時,隻要不改變volatile寫-讀的記憶體語義,編譯器可以根據具體情況省略不必要的屏障
  • JSR-133為什麼要增強volatile的記憶體語義?

    嚴格限制編譯器和處理器對volatile變量與普通變量的重排序,確定volatile的寫-讀和鎖的釋放-擷取具有相同的記憶體語義。

鎖的記憶體語義

鎖的釋放-擷取建立的happens-before關系
并發程式設計之知識梳理 第一部分全文内容引用
并發程式設計之知識梳理 第一部分全文内容引用
并發程式設計之知識梳理 第一部分全文内容引用
鎖記憶體語義的實作
并發程式設計之知識梳理 第一部分全文内容引用

concurrent包的實作

并發程式設計之知識梳理 第一部分全文内容引用
并發程式設計之知識梳理 第一部分全文内容引用

final域的記憶體語義

并發程式設計之知識梳理 第一部分全文内容引用

雙重檢查鎖定與延遲初始化

雙重檢查鎖定的由來
問題的根源
并發程式設計之知識梳理 第一部分全文内容引用
基于volatile的解決方案
并發程式設計之知識梳理 第一部分全文内容引用
基于類初始化的解決方案
并發程式設計之知識梳理 第一部分全文内容引用
解決方案的比較
并發程式設計之知識梳理 第一部分全文内容引用

Java中的線程

線程的狀态

有哪些?
并發程式設計之知識梳理 第一部分全文内容引用
變遷
并發程式設計之知識梳理 第一部分全文内容引用
并發程式設計之知識梳理 第一部分全文内容引用

Daemon線程

優先級

  • 程式正确性不能依賴線程的優先級高低

啟動和終止線程

建立
  • 實作Runnable接口的run方法
  • 繼承Thread類并重寫run的方法
  • 使用FutureTask方式,實作Callable接口的call方法
中斷
  • boolean isInterrupted():檢測目前線程是否被中斷,如果是傳回true,否則傳回false
  • boolean interrupted():檢測目前線程是否被中斷,如果是傳回true,否則傳回false。與isInterrupted不同的是,該方法如果發現目前線程被中斷,則會清除中斷标志,并且該方法是static方法,可以通過Thread類直接調用。另外從下面的代碼可以知道,在interrupted()内部是擷取目前調用線程的中斷标志而不是調用interrupted()方法的執行個體對象的中斷标志。
  • 從Java的API中可以看到,許多聲明抛出InterruptedException的方法(例如Thread. sleep(long millis)方法)這些方法在抛出InterruptedException之前,Java虛拟機會先将該線程的中斷辨別位清除,然後抛出InterruptedException,此時調用isInterrupted()方法将會傳回false。

線程間的通信

synchronized

底層實作

并發程式設計之知識梳理 第一部分全文内容引用
并發程式設計之知識梳理 第一部分全文内容引用

在Java虛拟機(HotSpot)中,monitor是由ObjectMonitor實作的(位于HotSpot虛拟機源碼ObjectMonitor.hpp檔案,C++實作的)

ObjectMonitor中有幾個關鍵屬性:

  • _owner:指向持有ObjectMonitor對象的線程
  • _WaitSet:存放處于wait狀态的線程隊列
  • _EntryList:存放處于等待鎖block狀态的線程隊列
  • _recursions:鎖的重入次數
  • _count:用來記錄該線程擷取鎖的次數

    ObjectMonitor中有幾個關鍵方法:

  • enter(TRAPS)
  • exit(TRAPS)
  • wait(jlong millis, bool interruptable, TRAPS)
  • nofity(TRAPS)

JVM的優化

  • 鎖更新
    并發程式設計之知識梳理 第一部分全文内容引用
  • 鎖消除

    JIT編譯器借助逃逸分析(Escape Analysis)技術來判斷同步塊所使用的鎖對象是否隻能夠被一個線程通路,如果被證明,就會取消對這部分代碼的同步。

  • 鎖粗化

    盡量減小鎖的粒度,可以避免不必要的阻塞。但是如果在一段代碼中連續的用同一個螢幕鎖反複的加鎖解鎖,甚至加鎖操作出現在循環體中的時候,就會導緻不必要的性能損耗,這種情況就需要鎖粗化。

等待/通知機制
并發程式設計之知識梳理 第一部分全文内容引用
  • 使用wait()、notify()和notifyAll()時需要先對調用對象加鎖
  • 調用wait()方法後,線程狀态由RUNNING變為WAITING,并将目前線程放置到對象的等待隊列
  • notify()或notifyAll()方法調用後,等待線程依舊不會從wait()傳回,需要調用notify()或notifAll()的線程釋放鎖之後,等待線程才有機會從wait()傳回
  • notify()方法将等待隊列中的一個等待線程從等待隊列中移到同步隊列中,而notifyAll()方法則是将等待隊列中所有的線程全部移到同步隊列,被移動的線程狀态由WAITING變為BLOCKED
  • 從wait()方法傳回的前提是獲得了調用對象的鎖
    并發程式設計之知識梳理 第一部分全文内容引用
Thread.join()的使用
  • 線程A執行了thread.join()語句
  • 目前線程A等待thread線程終止之後才從thread.join()傳回
ThreadLocal的使用

并發程式設計之知識梳理 第一部分全文内容引用

Lock接口

我有synchronized沒有的
特性 API
能響應中斷 lockInterruptbly()
非阻塞式的擷取鎖 tryLock()
支援逾時 tryLock(long time, timeUnit)
可實作公平鎖 ReentrantLock(ture)
可以綁定多個條件 newCondition()
API
并發程式設計之知識梳理 第一部分全文内容引用

隊列同步器(AQS)

基于模闆方法模式

AbstractQueuedSynchronizer

通路或修改同步狀态

并發程式設計之知識梳理 第一部分全文内容引用

可重寫的方法

并發程式設計之知識梳理 第一部分全文内容引用

模闆方法

并發程式設計之知識梳理 第一部分全文内容引用
實作分析

同步隊列(FIFO)

并發程式設計之知識梳理 第一部分全文内容引用

節點的屬性類型與名稱以及描述

并發程式設計之知識梳理 第一部分全文内容引用
并發程式設計之知識梳理 第一部分全文内容引用

隻有前驅節點是頭節點才能夠嘗試擷取同步狀态的原因:

  • 頭節點是成功擷取到同步狀态的節點,而頭節點的線程釋放了同步狀态之後,将會喚醒其後繼節點,後繼節點的線程被喚醒後需要檢查自己的前驅節點是否是頭節點。
  • 維護同步隊列的FIFO原則

獨占式同步狀态擷取與釋放

節點自旋擷取同步狀态

并發程式設計之知識梳理 第一部分全文内容引用

解釋:由于非首節點線程前驅節點出隊或者被中斷而從等待狀态傳回,随後檢查自己的前驅是否是頭節點,如果是則嘗試擷取同步狀态。

獨占式同步狀态擷取流程,也就是acquire(int arg)方法調用流程:

并發程式設計之知識梳理 第一部分全文内容引用
并發程式設計之知識梳理 第一部分全文内容引用

同步狀态釋放

并發程式設計之知識梳理 第一部分全文内容引用

共享式同步狀态擷取與釋放

獨占式逾時擷取同步狀态

并發程式設計之知識梳理 第一部分全文内容引用

重入鎖(ReentrantLock)

并發程式設計之知識梳理 第一部分全文内容引用

讀寫鎖(ReentrantReadWriteLock)

并發程式設計之知識梳理 第一部分全文内容引用
并發程式設計之知識梳理 第一部分全文内容引用

ReentrantReadWriteLock展示内部工作狀态的方法

并發程式設計之知識梳理 第一部分全文内容引用

LockSupport工具

并發程式設計之知識梳理 第一部分全文内容引用

LockSupport提供的阻塞和喚醒方法:

并發程式設計之知識梳理 第一部分全文内容引用

Condition接口

并發程式設計之知識梳理 第一部分全文内容引用
API
并發程式設計之知識梳理 第一部分全文内容引用
實作分析

同步隊列與等待隊列

并發程式設計之知識梳理 第一部分全文内容引用

目前線程加入等待隊列:

并發程式設計之知識梳理 第一部分全文内容引用

節點從等待隊列移動到同步隊列

并發程式設計之知識梳理 第一部分全文内容引用

引用

[1]: 連結 https://mp.weixin.qq.com/s/-PRq4ChaCkEFB_DJyyKhvg

[2]: 《Java并發程式設計的藝術》 方騰飛 魏鵬 程曉明

[3]: 《Java并發程式設計之美》翟陸續,薛賓田