偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

面試官:談談你對 Volatile 的理解吧

開發(fā) 后端
volatile 在指令之間插入內存屏障 + 緩存一致性協(xié)議,保證按照特定順序執(zhí)行和某些變量的可見性。volatile 通過 內存屏障通知 CPU 和編譯器阻止指令重排優(yōu)化來維持有序性。

前言

在之前的文章 深入分析 Synchronized 原理 介紹了 Synchronized是一種鎖的機制,存在阻塞和性能的問題,而 volatile 是 java 虛擬機提供的最輕量級的同步機制,volatile 主要提供修飾共享變量賦予 “可見性” 和 “有序性”。從簡單的 Demo 引出我們今天的主題 -- volatile。

Demo -- 多線程共享對象 控制執(zhí)行開關。

public class Demo {
private static boolean switchStatus = false;
public static void main(String[] args) {
new Thread(() -> {
System.out.println("開始工作");
while (!switchStatus) ;
System.out.println("結束工作");
}).start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
switchStatus = true;
System.out.println("命令停止工作");
}
}

本意是想通過 switchStatus 作為控制工作線程的開關,但是實際執(zhí)行后,會發(fā)現結果并沒有按照預期 輸出"結束工作",而是失聯(lián)了一樣停不下來了,在死循環(huán)中出不來了。

但是如果在上面的 Demo 進行稍微的修改即可滿足預期: private static volatile boolean switchStatus = false; 此時符合預期關閉開關時,工作線程也隨之關閉了。接下我會針對這2個現象原理進行解答,為了讀者更好的理解,得先引入幾個知識點(計算機內存模型、JMM-Java 內存模型)。

計算機內存模型

為了更好地理解后續(xù) JMM 和 volatile,我們先了解下計算機內存模型,簡單地介紹下:

程序執(zhí)行時,CPU接收到指令 需要進行計算時,讀取所需要的數據,會先嘗試從 CPU Cache 中獲取,若沒有再從主內存中獲取,計算完成后,將結果寫入 CPU Cache ,若沒有特殊指令的情況下,會根據操作系統(tǒng)自身定義的時間 一段時間會將 CPU Cache 刷新到主內存中(未被volatile 修飾的普通變量);當然遇到特殊的指令會將 CPU Cache 刷新到主內存中(被volatile 修飾的變量 就是依賴這個特性實現可見性)。

  • CPU:處理程序中各種指令,需要和CPU Cache 和 內存打交道。
  • CPU Cache:由于 CPU 和內存的速度差 幾個數量級,CPU 直接和內存打交道很浪費 CPU 性能,因此引入了 CPU Cache 降低 CPU 的性能損耗。
  • 緩存一致性協(xié)議/總線鎖機制:引入 CPU Cache降低了 CPU性能損耗的問題,同時引入了緩存不一致的問題,為了解決這個這個問題通過緩存一致性協(xié)議/總線鎖機制 進行解決。

總線鎖機制

CPU和其他功能部件是通過總線通信的,如果在總線加LOCK#鎖,那么在鎖住總線期間,其他CPU是無法訪問內存,這樣一來,效率就比較低了。因此需要進行優(yōu)化,細化控制鎖的粒度,我們只需要保證,對于被多個CPU緩存的同一份數據是一致的就行,所以引入了緩存鎖,他的核心機制就是緩存一致性協(xié)議。

緩存一致性協(xié)議

為了達成數據訪問的一致性,需要各個處理器在訪問內存時,遵循一些協(xié)議,在讀寫時根據協(xié)議來操作,常見的協(xié)議有,MSI,MESI,MOSI等等,最常見的就是MESI協(xié)議;MESI表示緩存行的四種狀態(tài)(modify、 Exclusive、Shared、 Invalid)。

嗅探技術

如何保證當前處理器的內部緩存、主內存和其他處理器的緩存數據在總線上保持一致的?多處理器總線嗅探。

在多處理器下,為了保證各個處理器的緩存是一致的,就會實現緩存緩存一致性協(xié)議,每個處理器通過嗅探在總線上傳播的數據來檢查自己的緩存值是不是過期了,如果處理器發(fā)現自己緩存行對應的內存地址被修改,就會將當前處理器的緩存行設置無效狀態(tài),當處理器對這個數據進行修改操作的時候,會重新從系統(tǒng)內存中把數據庫讀到處理器緩存中。

Java內存模型

  • Java虛擬機規(guī)范試圖定義一種Java內存模型,來屏蔽掉各種硬件和操作系統(tǒng)的內存訪問差異,以實現讓Java程序在各種平臺上都能達到一致的內存訪問效果。
  • 為了更好地執(zhí)行性能,java內存模型并沒有限制執(zhí)行引擎使用處理器的特定寄存器或緩存來和主內存打交道,也沒有限制編譯器進行調整代碼順序優(yōu)化。所以Java內存模型會存在緩存一致性問題和指令重排序問題的。
  • Java內存模型規(guī)定所有的變量都是存在主內存當中(類似于計算機模型中的物理內存),每個線程都有自己的工作內存(類似于計算機模型的高速緩存)。這里的變量包括實例變量和靜態(tài)變量,但是不包括局部變量,因為局部變量是線程私有的。
  • 線程的工作內存保存了被該線程使用的變量的主內存副本,線程對變量的所有操作都必須在工作內存中進行,而不能直接操作操作主內存。并且每個線程不能訪問其他線程的工作內存。

舉個例子:

# 初始值 
i = 0;
# 線程A 線程B同時進行操作
i = i + 1;

首先,執(zhí)行線程A從主內存中讀取到 i=0,到工作內存。然后在工作內存中,賦值 i+1,工作內存就得到 i=1,最后把結果寫回主內存。如果是單線程的話,該語句執(zhí)行是沒問題的。但是在多線程的情況下,線程B的本地工作內存和線程A的工作內存讀取的時間相同都是 i=0,但是線程A將 i=1寫入主內存中,線程B不知情的情況下,也做了 i+1 的操作,此時就出現可見性帶來問題了:連續(xù)2次的 i=i+1 最終的結果是1。

volatiole 可見性、有序性

在之前的文章 深入分析 Synchronized 原理 已經介紹過 原子性、可見性、有序性定義,這里也就不展開說了。

先說結論:依賴于 CPU 緩存一致性協(xié)議 和 內存屏障 解決了可見性的問題。

正常來說,volatile 基于緩存一致性協(xié)議就應該可以實現可見性(在上面已經介紹過 緩存一致性協(xié)議和嗅探技術),但是由于 Java 為了提高性能允許重排序(編譯器重排序 和 處理器重排序),因此需要通過內存屏障來防止重排序,來保證每個線程執(zhí)行的每個指令有一定的順序性。

java 內存屏障

java的內存屏障通常所謂的四種即 LoadLoad、StoreStore、 LoadStore、StoreLoad 實際上也是上述兩種的組合,完成一系列的屏障和數據同步功能。

  • LoadLoad屏障:對于這樣的語句Load1; LoadLoad; Load2,在Load2及后續(xù)讀取操作要讀取的數據被訪問前,保證Load1要讀取的數據被讀取完畢。
  • StoreStore屏障:對于這樣的語句Store1; StoreStore; Store2,在Store2及后續(xù)寫入操作執(zhí)行前,保證Store1的寫入操作對其它處理器可見。
  • LoadStore屏障:對于這樣的語句Load1; LoadStore; Store2,在Store2及后續(xù)寫入操作被刷出前,保證Load1要讀取的數據被讀取完畢。
  • StoreLoad屏障:對于這樣的語句Store1; StoreLoad; Load2,在Load2及后續(xù)所有讀取操作執(zhí)行前,保證Store1的寫入對所有處理器可見。它的開銷是四種屏障中最大的。在大多數處理器的實現中,這個屏障是個萬能屏障,兼具其它三種內存屏障的功能。

volatile 語義的內存屏障

  • 在每個 volatile 寫操作前插入 StoreStore 屏障,在寫操作后插入 StoreLoad 屏障。
  • 在每個 volatile 讀操作前插入 LoadLoad 屏障,在讀操作后插入 LoadStore 屏障。
  • 由于內存屏障的作用,避免了 volatile 變量和其它指令重排序、線程之間實現了通信,使得 volatile 表現出了鎖的特性。

舉一個 volatile 防止指令重排的場景

java 中 DLC單例模式 大家應該很熟悉了,只不過大家是否有注意到 uniqueInstance 被 volatile 修飾的作用嗎? 就是為了防止指令重排。

public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getInstance() {
if (uniqueInstance != null) {
synchronized (Singleton.class) {
if (uniqueInstance != null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}

初始化一個類,會產生多條匯編指令,總結下來主要執(zhí)行下面三點:

  1. 給 uniqueInstance 的實例分配內存。
  2. 初始化 Singleton 的構造器。
  3. 將 uniqueInstance 對象指向分配的內存空間(按順序到這步 uniqueInstance 初始化完成)。

理想的狀態(tài)下:1 -> 2 -> 3,但是 Java 為了提高性能允許重排序,可能會將初始化一個類的順序進行變化,比如:1 -> 3 -> 2,這種情況下就可能會出現NPE,修飾了volatile 防止重排序,避免獲取到 uniqueInstance 未初始化完成,導致NPE

最后簡單總結下:volatile 在指令之間插入內存屏障 + 緩存一致性協(xié)議,保證按照特定順序執(zhí)行和某些變量的可見性。volatile 通過 內存屏障通知 CPU 和編譯器阻止指令重排優(yōu)化來維持有序性。

責任編輯:姜華 來源: 今日頭條
相關推薦

2025-03-21 00:00:05

Reactor設計模式I/O 機制

2024-10-24 16:14:43

數據傳輸CPU零拷貝

2025-02-21 15:25:54

虛擬線程輕量級

2024-09-27 15:43:52

零拷貝DMAIO

2024-06-13 08:01:19

2024-08-27 12:36:33

2024-09-26 16:01:52

2024-08-26 14:52:58

JavaScript循環(huán)機制

2019-07-26 06:42:28

PG架構數據庫

2024-10-12 16:25:12

2021-11-25 10:18:42

RESTfulJava互聯(lián)網

2021-08-09 07:47:40

Git面試版本

2025-01-13 09:24:32

2025-04-09 00:00:00

2024-08-23 09:02:56

2020-12-01 08:47:36

Java異常開發(fā)

2020-06-12 15:50:56

options前端服務器

2021-11-05 10:07:13

Redis哈希表存儲

2020-06-19 15:32:56

HashMap面試代碼

2021-09-16 07:52:18

算法應用場景
點贊
收藏

51CTO技術棧公眾號