java進階(6)之從硬件底層剖析synchronized/volatile原理

已知:

  • java中的synchronized關鍵字能保證可見性,有序性,原子性;
  • volatile關鍵字能保證可見性,有序性。

問題:

  • 爲什麼java中的併發,在硬件層面不能保證,非要在JVM裏處理呢?
  • 它們在硬件層面是如何對應保證的呢?
  • java層面爲啥要加這兩個關鍵字才能保證java的併發特性呢?

指令重排序無法保證有序性

java中的一行行代碼,對應到硬件層面,就是一個個指令,現代處理器爲了加快編譯速度,有可能會亂序執行指令,這樣也就無法保證代碼的有序性了。
在這裏插入圖片描述

現代CPU緩存模型無法保證可見性和原子性

圖解CPU緩存模型

簡化版CPU緩存模型請移步:java進階(4)之volatile關鍵字深入詳解
在這裏插入圖片描述
1. 高速緩存中的tag表示數據所對應的內存地址,cacheline表示數據本身,flag表示數據狀態(E表示exclusive寫,S表示shared讀)

2. Java層面的讀寫數據,對應到硬件層面,就是主內存/高速緩存的讀寫;

3. 處理器與處理器之間的數據交互,或者說處理器與主內存的數據交互,是通過總線的嗅探機制來實現的,也可以說是緩存一致性協議(我把嗅探機制類比成java層面的EventBus);

4. 現代CPU爲了加快讀寫速度,加入了寫緩衝器和無效隊列,但是這樣的話,數據只有讀寫完成了,才能進入到高速緩存/主內存,這樣會導致數據無法及時更新,從而無法保證原子性,可見性。

內存屏障保障有序性和可見性

有序性保障

  • 每個volatile/synchronized寫操作前面,加StoreStore屏障,禁止上面的普通寫和volatile/synchronized重排;
  • 每個volatile/synchronized寫操作後面,加StoreLoad屏障,禁止跟下面的volatile/synchronized讀/寫重排;
  • 每個volatile/synchronized讀操作前面,加LoadLoad屏障,禁止上面的普通讀和volatile/synchronized讀重排;
  • 每個volatile/synchronized讀操作後面,加LoadStore屏障,禁止下面的普通寫和volatile/synchronized讀重排。

可見性保障

  • volatile/synchronized關鍵字修飾的變量或者代碼塊在加載之前,會去硬件底層執行refresh指令,將高速緩存/主內存的最新數據刷新到本地工作內存。
  • volatile/synchronized關鍵字修飾的變量或者代碼塊在加載之後,會去硬件底層執行flush執行,將數據刷到高速緩存/主內存中。

synchronized中CAS機制保障原子性

在這裏插入圖片描述
synchronized關鍵字修飾的代碼塊或者方法,會被指令monitorentermonitorexit包裹,包裹的模塊可以稱之爲ObjectMonitor,當有線程進入時,會執行CAS加鎖,從而保證了原子性。