成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

明明加了 volatile,為什么數據還是錯的?

開發 前端
HotSpot 會將這些讀寫操作轉換為對應平臺的內存屏障指令(如 x86 上的 LOCK 前綴),這會觸發 緩存一致性協議(MESI) 來確保其他 CPU 核心能感知這一變更。

“知道”?

如果每次都強制刷新主內存,性能代價是否太高?

有沒有一種機制,只在需要時才確保可見性,而不犧牲并發性能?

這就是 volatile 登場的理由。

它是怎么做到“可見”的?

我們知道,volatile 的核心承諾之一是 可見性。那它是怎么做到的?

當一個變量被聲明為 volatile 后,編譯器和 CPU 都會受到一系列“約束”:

  • 寫 volatile 變量時:JVM 會在生成的字節碼中插入一個 store barrier(寫屏障),強制將當前線程的工作內存中對應的變量刷新到主內存。
  • 讀 volatile 變量時:插入一個 load barrier(讀屏障),強制從主內存讀取變量,禁止從緩存中獲取舊值。

而在更底層的匯編層面,HotSpot 會將這些讀寫操作轉換為對應平臺的內存屏障指令(如 x86 上的 LOCK 前綴),這會觸發 緩存一致性協議(MESI) 來確保其他 CPU 核心能感知這一變更。

CPU緩存、多級緩存與主內存之間的一致性交互CPU緩存、多級緩存與主內存之間的一致性交互

CPU緩存、多級緩存與主內存之間的一致性交互

所以,當你寫了一個 volatile 變量,本質上你是在告訴 JVM 和 CPU:

“這個變量很重要,我要確保它對其他線程立即可見,別偷偷緩存。”

這機制本身非常高效,因為它避免了顯式加鎖,卻仍能在某些關鍵場景下確保同步。但問題來了:

那 volatile 能解決“并發寫”的問題嗎?

我們再看另一個經典的例子:

volatile int count = 0;

// 多線程執行:
count++;

你也許以為,volatile 保證了可見性,線程A加1后,線程B就能“看到”變化。但實際運行中,count 的結果常常是錯的,甚至比預期小很多。這是為什么?

我們來拆解一下count++的底層執行:

  1. 讀取 count
  2. 自增(加1)
  3. 寫回 count

這個操作看似一個語句,但其實是 三個獨立的步驟。在多線程環境中,多個線程可能在同一時間讀取到相同的舊值,然后各自加1,最終寫回,就發生了“覆蓋”。

于是我們意識到:

volatile 確保了“你讀到的是最新的值”,但不會阻止其他線程在你讀完和寫入之間“插一腳”。

這就是 volatile 的第二個重要特性:不保證原子性。

所以,如果你要保證 count++ 是線程安全的,volatile 是不夠的。你需要加鎖(synchronized)或使用原子類(如 AtomicInteger),這些機制提供了“操作不可分割”的原子語義。

那么它如何禁止“指令重排”?

還有一個非常重要但容易被忽視的 volatile 特性是:禁止指令重排序(只針對特定場景)。

你可能會問:什么是重排序?它又會帶來什么風險?

現代 CPU 和 JIT 編譯器為了優化性能,會調整指令執行順序,只要最終結果不變,它們就有理由這么做。但在并發環境中,這種“聰明”可能帶來災難。

比如,在 雙重檢查鎖中:

if (instance == null) {
    synchronized(...) {
        if (instance == null) {
            instance = new Singleton(); // 可能會被重排序
        }
    }
}

如果 instance 沒有被聲明為 volatile,那么這段代碼可能會出現 對象引用先被賦值,再初始化成員變量 的情況,導致另一個線程拿到的是“半初始化”的對象。

這是因為 instance = new Singleton() 在字節碼層面大致分為三步:

  1. 分配內存
  2. 調用構造方法初始化
  3. 將引用賦值給 instance

在沒有 volatile 的保護下,步驟2和3可能被重排序,最終讓另一個線程看到一個“不是 null 但沒初始化”的引用。

指令重排序示意圖:構造順序 vs 實際執行順序指令重排序示意圖:構造順序 vs 實際執行順序

指令重排序示意圖:構造順序 vs 實際執行順序

而 volatile 則通過內存屏障來禁止這些特定的重排,從而讓雙檢鎖的懶加載寫法變得安全。

volatile 是不是一種“輕量級鎖”?

這個說法常常被提起,但它并不準確。我們可以這么理解:

  • 鎖(如 synchronized) 提供了:可見性 + 原子性 + 互斥執行
  • volatile 僅提供:可見性 + 有序性(部分)

也就是說,volatile 是一個比鎖“輕”的同步工具,但它并不能替代鎖。它適合那些:

  • 只有一個寫線程,多讀線程(典型場景如配置更新)
  • 狀態標志控制(如停止線程、開關變量)
  • 雙檢鎖中的對象引用

但一旦涉及多個線程同時修改變量(如計數器、列表增刪),就必須用到真正的互斥機制。

總結:volatile 能做什么,不能做什么?

我們回頭看 volatile,其實它解決了并發編程中最“微妙”的部分之一 —— 內存可見性和有序性。它的設計精妙之處在于:

  • 用極低的開銷,換來了主內存與線程緩存之間的數據同步
  • 在特定場景下,用內存屏障保障了代碼執行順序的可預期性

但它的能力也有明確邊界:

  • 不提供原子性
  • 無法互斥訪問臨界區
  • 不能替代鎖

如果你記住這一點,你就不會再對“加了 volatile 為什么還錯”感到困惑了。

思考一個延伸問題:

如果 AtomicInteger 內部用了 volatile,又怎么實現了原子性?它到底是如何做到“又輕量又安全”的?

下次,我們不妨走進 CAS(Compare-And-Swap)的世界,看看它和 volatile 是如何攜手,讓并發編程“快且對”的。

責任編輯:武曉燕 來源: 小龍coding
相關推薦

2020-10-29 09:19:11

索引查詢存儲

2022-08-04 08:22:49

MySQL索引

2024-08-19 09:43:00

2024-12-02 08:01:47

加鎖高并發程序

2022-07-20 07:32:46

Prototypevalue?容器

2021-07-28 21:32:43

手機蘋果小米

2021-02-23 08:02:23

線程volatileinterrupt

2021-02-07 10:17:22

項目架構技術管理

2020-08-24 15:49:28

代碼bug出錯

2021-06-28 07:13:35

SQL語句索引

2023-12-08 08:01:14

Redis存儲內存

2022-05-26 09:24:09

volatile懶漢模式

2021-07-19 09:00:24

微軟Windows 11Windows

2019-06-05 10:23:00

2021-03-05 08:29:20

DeleteMysql數據結構

2020-03-05 16:55:56

索引數據庫SQL

2017-05-15 16:30:49

NoSQLMySQLOracle

2020-03-11 16:20:03

Serializabl接口Java

2017-09-18 14:39:31

溝通培訓學習

2015-01-23 09:37:22

IPv6IPv4
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 国产精品一二三区 | 免费观看黄色片视频 | 免费黄色av | 久久久九九 | 久久国产欧美日韩精品 | 91久久久久 | 国产精品久久久久久久久久尿 | 一区二区三区亚洲视频 | 欧美视频三区 | 免费黄视频网站 | 亚洲一区国产精品 | 免费一区二区 | 日韩欧美国产一区二区三区 | 日日想夜夜操 | 色综合久久久久 | 在线观看中文字幕亚洲 | 激情一区二区三区 | 羞羞视频免费观 | 亚洲视频在线观看 | 一区二区视频 | 亚洲高清一区二区三区 | 亚洲一区二区视频 | 91人人看 | 欧美视频一区二区三区 | 久久久久国产精品 | 日本超碰 | 亚洲国产伊人 | 日韩成人影院 | 一区二区三区四区免费在线观看 | 欧美老少妇一级特黄一片 | 欧美一区二区三区视频 | 亚洲一区精品视频 | 欧美精品在线播放 | 国产自产21区| 国产一区高清 | 日韩欧美手机在线 | 成人精品久久日伦片大全免费 | 中文字幕日韩av | 日韩欧美一区二区三区四区 | 老熟女毛片 | 永久看片 |