Java併發程式設計學習體系
一、可見性、原子性、有序性(三大特性)
1)發生背景
由於cpu、記憶體、io裝置的速度差異,做了以下優化
1、cpu 增加了快取,以均衡與記憶體的速度差異
2、作業系統增加了程序、執行緒,以分時複用cpu,進而均衡cpu與io裝置的速度差異
3、編譯程式優化指令執行次序,使得cpu快取能夠得到更加合理利用
2)帶來的問題(引出三大特性)
現實中的併發問題往往是三種問題的綜合症
1、快取導致的可見性問題
2、執行緒切換帶來的原子性問題
原子性外在表現是不可分割,本質是多個資源有一致性要求 ,保證中間狀態對外不可見
3、編譯優化,指令優化帶來的有序性問題
舉例:雙重檢查建立單例物件 可能存在問題,所以要對instance進行volatile語義宣告,就可以禁止指令重排序
3)解決辦法
本質:通常是按需禁用快取以及編譯優化,來保證可見性和有序性
保證對共享變數的修改 是互斥的(同一時刻只有一個執行緒執行),來保證原子性
通俗做法分三種:
(1) vollatile
volatile強制所修飾的變數及它前邊的變數重新整理至記憶體,並且volatile禁止了指令的重排序,解決可見性和有序性問題
(2) synchronized
必須保證是同一把鎖,互斥,本質上是保證序列執行
在解鎖的時候,JVM需要強制重新整理快取,使得當前執行緒所修改的記憶體對其他執行緒可見
(3) final
當一個物件包含final修飾的例項欄位時,其他執行緒能夠看到已經初始化的final例項欄位,這是安全的
二、java記憶體模型(兩大核心之一)
1)Java記憶體模型定義了執行緒和記憶體的互動方式
在JMM抽象模型中,分為主記憶體、工作記憶體。主記憶體是所有執行緒共享的,工作記憶體是每個執行緒獨有的。執行緒對變數的所有操作(讀取、賦值)都必須在工作記憶體中進行,不能直接讀寫主記憶體中的變數。並且不同的執行緒之間無法訪問對方工作記憶體中的變數,執行緒間的變數值的傳遞都需要通過主記憶體來完成
在這裡的工作記憶體特指實體記憶體,是cpu的暫存器和快取記憶體的抽象描述。主記憶體相當於硬體的記憶體。
2) 記憶體間的互動操作
-
Lock(鎖定):主記憶體變數鎖定
-
Unlock(解鎖):主記憶體變數解鎖
-
Read(讀取):主記憶體變數讀取,將值傳給工作記憶體,待Load
-
Load(載入):工作記憶體變數載入,將Read讀到的值,存放到本地副本
-
Use(使用):把工作記憶體的變數傳遞給執行引擎
-
Assign(賦值):把從執行引擎接收到的的值賦值給工作記憶體變數
-
Store(儲存):把工作記憶體的變數值傳遞給主記憶體,以便後續的write使用
-
Write(寫入):用於主記憶體變數,把store獲得的變數的值放入主記憶體變數
3)記憶體模型解決併發問題主要採用兩種方式:限制處理器優化和使用記憶體屏障
語義上,記憶體屏障之前的所有寫操作都要寫入記憶體;記憶體屏障之後的讀操作都可以獲得同步屏障之前的寫操作的結果。因此,對於敏感的程式塊,寫操作之後、讀操作之前可以插入記憶體屏障。
三、 happens-before規則(兩大核心之一)
本質 :前面一個操作的結果對後續操作是可見的
1)程式的順序性規則
單執行緒不會出現有序性問題,原來是happens-before第一條規則限制住了編譯器的優化
2)volatile 變數規則
寫先於讀指的是不會因為cpu快取,導致a執行緒已經寫了,但是b執行緒沒讀到的情況
3) 管程中鎖的規則
4)執行緒 start() 規則
5)執行緒 join() 規則
6)執行緒中斷規則
7) 物件終結規則
8)傳遞性(最重要的特性)
四、分工、同步、互斥(構建體系中的三個核心問題)
參考資料:Java併發程式設計實戰-王寶令
如果喜歡,歡迎關注我的公眾號:喬志勇筆記