一篇文章看懂Java併發和執行緒安全(二)
【本文轉自爪哇筆記 作者:冷血狂魔 原文連結:https://mp.weixin.qq.com/s/QDioJjwBC6GImGkfYLS7rw】
前言
上一篇博文《一篇文章看懂Java併發和執行緒安全(一)》講述了多執行緒中,程式總不能按照我們所看到的那樣執行,必須保證共享資料的可見性和執行臨界區程式碼的有序性,才能讓多執行緒程式執行成我們想要的樣子,本篇部落格將繼續深入講解一個有序而又亂序的Java世界。
本部落格重點導讀
工作記憶體與主記憶體的資料交換的細節
指令重排序與記憶體屏障
volatile、final、鎖的記憶體語義
as-if-serial、happens-before
進入細節
主記憶體與工作記憶體互動協議
JMM定義了8種基本操作來完成,主記憶體、工作記憶體和執行引擎之間的互動,分別是lock、unlock、read、load、use、assign、store、write,虛擬機器的實現向程式設計師保證每一種操作都是原子的,不可分割,對於double和long型別的64為變數不做保證。瞭解了這些,有助於幫我們理解記憶體屏障。
別看有8個操作,實際上是成對定義的連貫操作。我們具體來看怎麼記憶。
針對於主記憶體的單獨操作lock和unlock
·lock:作用於主記憶體、把變數標示為執行緒獨佔
·unlock:作用於主記憶體、釋放鎖定狀態
主記憶體到工作記憶體的讀交換
·read:作用於主記憶體,把主記憶體變數傳遞給工作記憶體
·load:作用於工作記憶體,把read操作傳過來的值放入工作記憶體
工作記憶體到主記憶體的寫交換
·store:作用於工作記憶體,把工作記憶體變數傳遞給主記憶體
·write:作用於主記憶體,把store過來的值寫入主記憶體變數
工作記憶體和執行引擎的資料交換
·use:作用於工作記憶體,把工作記憶體變數傳遞給執行引擎
·assign:作用於工作記憶體,把執行引擎的值賦給工作記憶體變數
上述的互動關係,可以用如下的圖來表示:
總體來說,工作記憶體和主記憶體的資料交換讀寫都是用兩組操作來完成,而執行引擎和工作記憶體的資料交換由兩個操作完成。當然,上述的8種操作必須滿足一些規則,這裡列舉一些我認為重要的,例如:
·read和load、store和write必須同時出現
·對變數執行lock操作,會清空工作記憶體中快取的該值,對變數執行unlock操作,必須先把值同步回主記憶體。
廢了這麼大的篇幅,講我們Java程式設計師並不關心的資料交換細節,是為了幫助我們理解後面的記憶體屏障,繫好安全帶,我們繼續來看一個完全錯亂的Java微觀世界。
亂序的Java世界
在單執行緒的世界裡,JMM向我們保證執行的正確性,那麼我們可以邏輯的認為程式碼是根據我們編寫的順序執行。那麼在多執行緒的世界裡,站一個執行緒的視角看另一個執行緒,我們將完全看不清執行的順序。並且也看不到對方執行結果。請看下面的程式碼:
假設有兩個執行緒A、B分別要執行write和read方法,A先進去執行、B隨後執行,先拋開a、b執行緒可見性問題,假設a、b對執行緒立即可見。最後c值是多少?可能是1,可能是2,甚至可能是0。接下來具體分析一下為什麼。
站在B的視角看,它看不清a=1和b=1誰先執行,由於指令重排序,很可能b=1先執行,請看下錶:
站在B執行緒的視角,B執行緒中read方法裡的程式碼是否會重排序呢,雖然這個方法的兩句話存在依賴關係,JMM支援不改變結果的指令重排,JMM無法預先判斷是否有其他執行緒在修改a的值,所以可能會重排,並且處理器會用猜測執行來重排。請看下錶:
指令重排序讓執行緒看不清對方執行緒的執行順序,也就是亂序的,那麼會有哪些級別的指令重排序呢?有三種:編譯器重排序、指令級重排序、記憶體級重排序。
記憶體屏障
指令重排序會導致多執行緒執行的無序,那麼JMM會禁止特定型別的指令重排序,JMM通過記憶體屏障來禁止某些指令重排序,那麼有哪些記憶體屏障呢?總共4類
·LoadLoad:前面的load會先於後面的load裝載
·StoreStore:前面的store會先於後面的store執行,也就是保證記憶體可見性
·LoadStore:前面的load先於後面的store執行
·StoreLoad:前面的store先於後面的Load執行
接下來分別看volatile、final、鎖,都有哪些記憶體語義,加了哪些記憶體屏障。
volatile
·對volatile變數的寫操作,前面插入StoreStore屏障,防止和上面的寫發生重排序;後面插入StoreLoad屏障,防止和後面的讀寫發生重排序。
·對volatile變數的讀操作,後面會插入兩個屏障,分別是LoadLoad、LoadStore,說白了就是,我是volatile變數,不管你下面的變數是讀或者寫,我都要先於你讀。
final
final本質上定義是final域與構造物件的引用之間的記憶體屏障。
在建構函式對final變數的寫人,與對建構函式物件引用的讀,不能重排序,本質上是插入了storeStore屏障,保證物件引用被讀之前,已經對final變數進行了寫人。這裡特別注意指標逃逸。
讀含有final變數的物件的引用,與讀final變數不能指令重排序,插入loadload屏障,保證先讀到物件引用,在讀final變數的值,也就是隻要物件構造完成,並且在建構函式中將final值寫入,另外一個執行緒肯定可以讀到,這是JMM的保證。
鎖
ReentrantLock中 有個private volatile int state,本質上是用的volatile的記憶體語義,這裡就省略講了。
as-if-serial、happens-before
前面說這麼多,指令重排序重排序,弄亂了Java程式,JMM提供volatile、final、鎖來禁止某些指令重排序,那麼記住這些重排序規則並非簡單的事,JMM用另外一種好記的理論來幫助程式設計師記憶。
as-if-serial:用通俗的話來解釋一下,單線中,程式邏輯的以我們看到的順序執行,這裡只是可以邏輯的認為順序執行,其實也會有不影響結果的指令重排,例如:
int i=1;int j=2;int a=i*j;
這裡i=1,j=1重排不影響結果,那麼實際上JMM是允許的。 有了as-if-serial,在單執行緒中,程式設計師不用擔心指令重排和記憶體可見性問題。
happens-before:happens-before保證如果A、B兩個操作存在happens before關係,那麼A操作的結果一定對B可見,有了可見性的保證,在加上正確的同步,就能寫出執行緒安全的程式碼。JSR133定義了哪些天然的happens-before關係呢?請看下面:
·一個執行緒內,每個操作happens-before後面的操作
·unlock操作happens-before對這個這個鎖的lock操作
·volatile寫操作happens-before讀操作
·執行緒的start方法happens-before此執行緒的所有其他操作
·執行緒所有操作happens-before對此執行緒的終止監測,例如,A執行緒呼叫B執行緒的join方法,如果join返回,那麼B執行緒的所有操作必定完成,且B執行緒的所有操作的資料必定對A執行緒可見。
·傳遞性,A happens-before B、B happens-before C,那麼A happens-before C
最後總結一下,上一篇文章中圍繞可見性和執行臨界區程式碼的順序性進行了說明,本篇文章,主要說的是可見性,就本質而言,加記憶體屏障,就是為了保證前面的操作對後面的操作可見,也就是我不能和你順序弄亂了,我得看著你怎麼執行,happens-before是JMM對Java程式設計師的承諾,記住這些規則,配合鎖,必定執行緒安全。
最後還有兩句話
在本執行緒內看,所有的操作都是有序的,這是as-if-serial的保證。
一個執行緒看另一個執行緒,所有的操作都是無序的,主要是兩方面所致,一方面是指令重排序,另一方面是不知道工作記憶體的值什麼時候同步到主記憶體。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31137683/viewspace-2157559/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 一篇文章看懂Java併發和執行緒安全(一)Java執行緒
- Java併發專題(二)執行緒安全Java執行緒
- 多執行緒與高併發(二)執行緒安全執行緒
- Java併發實戰一:執行緒與執行緒安全Java執行緒
- Java併發-執行緒安全的集合類Java執行緒
- Java 併發:執行緒、執行緒池和執行器全面教程Java執行緒
- Java多執行緒詳解——一篇文章搞懂Java多執行緒Java執行緒
- Java併發程式設計之執行緒安全、執行緒通訊Java程式設計執行緒
- 【JAVA併發第一篇】Java的程式與執行緒Java執行緒
- 【JAVA併發第四篇】執行緒安全Java執行緒
- JAVA多執行緒和併發基礎Java執行緒
- Java高併發與多執行緒(二)-----執行緒的實現方式Java執行緒
- JAVA多執行緒併發Java執行緒
- 併發程式設計之多執行緒執行緒安全程式設計執行緒
- 併發與多執行緒之執行緒安全篇執行緒
- Java併發(四)----執行緒執行原理Java執行緒
- Java併發(十七)----變數的執行緒安全分析Java變數執行緒
- Java併發程式設計之執行緒篇之執行緒簡介(二)Java程式設計執行緒
- Java併發(一)----程式、執行緒、並行、併發Java執行緒並行
- Java多執行緒和併發問題集Java執行緒
- 【JAVA併發第二篇】Java執行緒的建立與執行,執行緒狀態與常用方法Java執行緒
- Java併發——執行緒池ThreadPoolExecutorJava執行緒thread
- Java併發系列 — 執行緒池Java執行緒
- java多執行緒與併發 - 執行緒池詳解Java執行緒
- 《Java併發程式設計實戰》 第二章:執行緒安全性Java程式設計執行緒
- 一文看懂JUC多執行緒及高併發執行緒
- Java併發程式設計:Java執行緒Java程式設計執行緒
- java多執行緒與併發 - 併發工具類Java執行緒
- [Java併發]執行緒的並行等待Java執行緒並行
- 執行緒安全(二)執行緒
- Java併發(十六)----執行緒八鎖Java執行緒
- Java執行緒的併發工具類Java執行緒
- 啃碎併發(五):Java執行緒安全特性與問題Java執行緒
- 併發程式設計與執行緒安全程式設計執行緒
- Java併發程式設計(二)如何保證執行緒同時/交替執行Java程式設計執行緒
- java併發筆記之java執行緒模型Java筆記執行緒模型
- Java併發指南1:併發基礎與Java多執行緒Java執行緒
- Java執行緒安全Java執行緒