JUC多线程:JMM内存模型与volatile内存语义
一、JMM 內存模型:
1、什么是 JMM 內存模型:
????????Java 內存模型是 Java 虛擬機定義的一種多線程訪問 Java 內存各個變量的訪問規范,主要圍繞如何解決并發過程中的原子性、可見性、有序性這三個問題來解決線程的安全問題。
????????Java 內存模型將內存分為了主內存和工作內存(也稱為棧空間)。主內存存放所有的共享變量,所有線程都可以訪問。每個線程都有自己的工作內存,存儲了該線程使用到的變量的副本,線程對變量的所有操作都必須在自己的工作內存中完成,不能直接操作主存中的變量。操作時,首先將變量從主內存拷貝到自己的工作內存中,然后在自己的工作內存中對變量進行操作,操作完成后再將變量寫回主存。不同的線程間也無法直接訪問對方的工作內存的變量,線程間的變量值的傳遞必須通過主內存來完成。
(1)原子性:原子性指的是一個操作是不可中斷的,即使是在多線程環境下,一個操作一旦開始就不會被其他線程影響。
(2)可見性:可見性指的是,當一個線程修改了某個共享變量的值,其他線程能夠馬上得知這個修改的值。
串行程序不存在可見性問題,因為在任何一個操作中修改了某個變量的值,后續的操作中都能讀取這個修改過的變量值。但在多線程環境中可就不一定了,因為線程對共享變量的操作都是拷貝到各自的工作內存中進行操作后才寫回到主內存中的,這就可能存在一個線程A修改了共享變量x的值,還未寫回主內存時,另外一個線程B又對主內存中同一個共享變量x進行操作,但此時A線程工作內存中共享變量x對線程B來說并不可見,這種工作內存與主內存同步延遲現象就造成了可見性問題,另外指令重排以及編譯器優化也可能導致可見性問題
(3)有序性:對于多線程環境,因為程序編譯成機器碼指令后可能會出現指令重排現象,重排后的指令與原指令的順序未必一致,有可能出現亂序現象
指令重排序:計算機在執行程序時,為了提高性能,編譯器和處理器的常常會對指令做重排,在單線程條件下,指令重排序可以保證執行結果的一致性,但是在多線程條件下,這些重排優化可能會導致程序出現內存可見性問題,不能保證多線程間語義一致性
2、原子性、可見性、有序性問題的解決措施:
(1)原子性問題:除了 JVM 自身提供對基本數據類型讀寫操作的原子性外,對于方法級別或者代碼級別的原子性操作,可以使用 synchronized 關鍵字或者重入鎖 ReentrantLock 保證程序執行的原子性。
(2)可見性問題:工作內存與主內存同步延遲現象導致的可見性問題,可以使用 synchronized 關鍵字、Lock 或者 volatile 關鍵字解決,它們都可以使一個線程修改后的變量立即對其他線程可見。
(3)有序性問題:可以利用 volatile、synchronized 關鍵字解決。
3、JMM 的 as-if-serial 規則和 happens-before 規則:
(1)as-if-serial:無論編譯器和處理器如何進行重排序,單線程程序的執行結果不會改變。
(2)happens-before:在多線程程序開發中,如果僅靠 synchronized 和 volatile 關鍵字來保證原子性、可見性以及有序性,那么編寫并發程序可能會顯得十分麻煩,所以 Java 內存模型中提供了內置的 happens-before 規則來輔助處理前面提到的問題,它是判斷數據是否存在競爭、線程是否安全的依據,從而保證線程安全。一個操作 happens-before 另一個操作,表示第一個的操作結果對第二個操作可見,并且第一個操作的執行順序也在第二個操作之前。但這并不意味著 JVM 必須按照這個順序來執行程序。如果重排序后的執行結果與按 happens-before 關系執行的結果一致,JVM 也允許重排序的發生。happens-before 原則內容如下:
-
程序順序原則:即在一個線程內必須保證語義串行性,也就是說按照代碼順序執行。
-
鎖規則:解鎖(unlock)操作必然發生在后續的同一個鎖的加鎖(lock)之前,也就是說,如果對于一個鎖解鎖后,再加鎖,那么加鎖的動作必須在解鎖動作之后(同一個鎖)。
-
volatile 規則:volatile 變量的寫,先發生于讀,這保證了volatile變量的可見性,簡單的理解就是,volatile 變量在每次被線程訪問時,都強迫從主內存中讀該變量的值,而當該變量發生變化時,又會強迫將最新的值刷新到主內存,任何時刻,不同的線程總是能夠看到該變量的最新值。
-
線程啟動規則:線程的 start() 方法先于它的每一個動作,即如果線程A在執行線程B的 start() 方法之前修改了共享變量的值,那么當線程B執行 start() 方法時線程A對共享變量的修改對線程B可見。
-
傳遞性:A先于B ,B先于C,那么A必然先于C
-
線程終止規則:線程的所有操作先于線程的終結,Thread.join() 方法的作用是等待當前執行的線程終止。假設在線程B終止之前,修改了共享變量,線程從線程B的 join() 方法成功返回后,線程B對共享變量的修改將對線程A可見。
-
線程中斷規則:對線程 interrupt() 方法的調用先行發生于被中斷線程的代碼檢測到中斷事件的發生,可以通過 Thread.interrupted() 方法檢測線程是否中斷。
-
對象終結規則:對象的構造函數執行,結束先于 finalize() 方法。
二、volatile 內存語義:
1、volatile 的作用:
????????volatile 是 Java 虛擬機提供的輕量級同步機制,是線程不安全的,volatile 跟可見性和有序性有關,被 volatile 修飾的共享變量,具有以下兩個作用:
(1)保證不同線程對該變量操作的內存可見性:當變量被 volatile 修飾時,那么對它的修改會立刻刷新到主存,同時其他線程操作 volatile 變量時,JMM 會把該線程對應的工作內存置為無效,那么該線程就只能從主存中重新讀取共享變量,保證讀取到最新值
通過 synchronized 和 Lock 也能夠保證可見性,線程在釋放鎖之前,會把共享變量值都刷回主存,但是相比于 volatile,synchronized 和 Lock 的開銷都更大。
(2)禁止指令重排序
2、內存屏障:
????????volatile 在內存中的語義是通過內存屏障實現,即可見性和禁止重排優化。把加入 volatile 關鍵字的代碼和未加入 volatile 關鍵字的代碼都生成匯編代碼,會發現加入 volatile 關鍵字的代碼會多出一個內存屏障指令,它是一個 CPU 指令。內存屏障提供了以下功能:
-
告訴編譯器和處理器,重排序時不能把后面的指令重排序到內存屏障之前的位置,從而避免多線程環境下出現亂序執行現象
-
保存某些變量的內存可見性,利用該特性實現 volatile 的內存可見性
3、volatile 的原子性:
????????volatile 的兩點內存語義能保證可見性和有序性,但是不能保證原子性:對單個 volatile 變量的讀/寫具有原子性,但是對于類似 volatile++ 這樣的復合操作就無能為力了,要想保證原子性,只能借助于 synchronized、Lock 或者并發包下的 atomic 的原子操作類了。
4、volatile使用場景:
(1)狀態量標記:這種對變量的讀寫操作,標記為 volatile 可以保證修改對線程立刻可見,效率也比 synchronized、Lock 有一定的提升。
public class VolatileSafe { ?volatile boolean close; ?public void close(){close=true;} ?public void doWork(){while (!close){System.out.println("safe....");}} }????????對于 boolean 變量 close 值的修改屬于原子性操作,因此可以通過使用 volatile 修飾變量 close,使用該變量對其他線程立即可見,從而達到線程安全的目的。
(2)單例模式的實現,典型的雙重檢查鎖定(DCL):
????????這是一種懶漢的單例模式,使用時才創建對象,而且為了避免初始化操作的指令重排序,給 instance 加上了 volatile。指令重排序會導致,當一條線程訪問的 instance 不為 null 時,但是實際上 instance 實例未必已初始化完成,也就造成了線程安全問題。
public class DoubleCheckLock {//禁止指令重排優化private volatile static DoubleCheckLock instance; ?private DoubleCheckLock(){}public static DoubleCheckLock getInstance(){ ?//第一次檢測if (instance==null){//同步synchronized (DoubleCheckLock.class){if (instance == null){//多線程環境下可能會出現問題的地方instance = new DoubleCheckLock();}}}return instance;} }單例模式的線程安全性:某個類的實例在多線程環境下只會被創建一次出來。單例模式有很多種的寫法:
-
(1)餓漢式單例模式的寫法:線程安全
-
(2)懶漢式單例模式的寫法:非線程安全
-
(3)雙檢鎖單例模式的寫法:線程安全
推薦文章:https://blog.csdn.net/javazejian/article/details/72772461
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的JUC多线程:JMM内存模型与volatile内存语义的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JUC多线程:synchronized锁
- 下一篇: JUC多线程:AQS抽象队列同步器原理