【多线程高并发】深入浅出volatile关键字
1.來自我的靈魂拷問
| 你是否經常碰到volatile關鍵字? volatile關鍵字的作用是什么? JMM層面如何理解volatile關鍵字? 你是否探究過volatile在匯編層面的實現? |
如何看待下面兩行代碼?
private int num = 0; private volatile int num2 = 0;2.volatile關鍵字的作用
(1) 保證了線程間共享變量的可見性
(2) 禁止指令重排
可見性問題是怎么產生的?
/*** @author:Ronin* @since:2021/12/7* @email:1817937322@qq.com*/ public class Visibility {private static boolean initFlag = false;public static void main(String[] args) throws InterruptedException {new Thread(() -> {while (!initFlag) {}}).start();TimeUnit.SECONDS.sleep(1);new Thread(() -> {initFlag = true;System.out.println(Thread.currentThread().getName() + "線程將flag修改為了true!");}).start();} }程序將一直處于運行狀態,雖然線程2修改了flag為true,但是線程1是不可見的;如果你不理解JMM中的可見性問題,請移步:深入淺出JMM-Java線程內存模型。
當我加入volatile關鍵字之后會出現什么情況呢?
程序將正常退出!
3.在JMM中volatile關鍵字怎么保證可見性的呢?
主要是通過MESI緩存一致性協議來實現的;當線程2對initFlag修改為true之后,處理器2會立即將修改后的數據從緩存中同步到主內存,在這個同步的過程中會經過總線,會被處理器1的總線嗅探機制監聽到initFlag發生了變化,處理器1會立即將緩存的initFlag=false失效。等到下次再使用initFlag時,需要重新從主內存讀取。這樣就保證了線程間共享變量的可見性。
4.對以上代碼進行反匯編處理,查看底層volatile的實現原理
教你幾步將Java代碼生成匯編指令:https://blog.csdn.net/Kevinnsm/article/details/121695215?spm=1001.2014.3001.5502
Ctrl+F搜索lock關鍵字
可以看出第十五行Java代碼在匯編指令中使用lock指令前綴
private static volatile boolean flag = true;繼續向下搜索lock出現的地方
可以看出第25行代碼在底層匯編也使用了lock指令前綴
將flag修改為了false,使用lock前綴之后,CPU會將數據立即刷新到主存中,然后其他CPU根據總線嗅探機制,檢測到flag值發生了變化,會立即將工作內存中的flag失效(因為同步到主內存需要經過總線)【多核計算機中,會有多個處理器,我這里說的CPU不是同一個】
5.指令重排機制
“你看到的不一定是你以為的!”,用這句話修飾指令重排非常貼切。我們在語言層面編寫代碼時是按照思維和習慣去編寫的,但編譯器和CPU執行時的順序和我們編寫的順序可能不一致。這是因為每個層面都有各自關注的事情,編譯器和CPU可能會對我們的代碼先優化再執行,以提高執行效率。
所以說指令重排是為了提高機器的執行效率而提出的一種的措施。如下圖所示,左邊是原來的代碼,經過編譯器優化可能會變成右邊的代碼
TODO:指令重排待寫…
分析如下代碼
import org.openjdk.jcstress.annotations.*; import org.openjdk.jcstress.infra.results.I_Result;/*** @author:Ronin* @since:2021/12/2* @email:1817937322@qq.com*/ @JCStressTest @State @Outcome(id = {"1", "4"}, expect = Expect.ACCEPTABLE, desc = "這是期待的結果") @Outcome(id = "0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "這是不期待的結果") public class VisibilityTest {/*** 結果有三種可能 4, 1,0(這個0是因為指令重排)*/private int a = 0;private boolean flag = false;@Actor //線程1public void method(I_Result result) {if (flag) {result.r1 = a * 2;} else {result.r1 = 1;}}@Actor //線程2public void method2(I_Result result) {a = 2; //這個地方有可能會發生指令重排,也就是a=2和flag=true互換flag = true;}}jcstress并發測試工具使用教程詳解:https://blog.csdn.net/Kevinnsm/article/details/121685052?spm=1001.2014.3001.5501
上述代碼使用jcstress工具對代碼進行數億的測試【注意測試時稍微耗電】,統計結果如下
可以看出出現了結果為0的情況,這是因為發生了指令重排,使得下面兩行代碼發生了替換
6.volatile關鍵字總結
(1) 保證了線程間共享變量的可見性
(2) 禁止指令重排
總結
以上是生活随笔為你收集整理的【多线程高并发】深入浅出volatile关键字的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【多线程高并发】深入理解JMM产生的三大
- 下一篇: 【LeetCode】LeetCode之打