趣談Java變量的可見性問題

        瞭解過多線程的我們,對synchorized,sleep和valatile都比較瞭解,但是當這三個名詞和“Java變量得可見性”的話題聯繫在一起不知道大家是否還可以保持大腦清晰???

       最近看到一個關於Java變量可見性的問題,感覺比較新鮮就查了一些資料,下面分享給大家:

       首先給大家看一段關於多線程的執行代碼:

package com.test;
import java.util.concurrent.TimeUnit;
 
public class test1 {
 
    private static boolean is = true;
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                int i = 0;
                while(test1.is){
 
                   i++;
 
                   1 //synchronized (this) { } 會強制刷新主內存的變量值到線程棧?
                   2 //System.out.println("1"); println 是synchronized 的,會強制刷新主內存的變量值到線程棧?
                   3 //sleep 會從新load主內存的值? 
                     //    try {
                     //       TimeUnit.MICROSECONDS.sleep(1);
                     //   }catch (InterruptedException e) {
                     //      e.printStackTrace(); 
                     //   }
                } 
            }
        }).start();
         try {
            TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();  
            }
        new Thread(new Runnable() {
            @Override
            public void run() {
                is = false;  //設置is爲false,使上面的線程結束while循環
            }
        }).start();
    }
}
         在運行當前代碼時,我們會發現程序可以正常運行,但是如果取消註釋行(1-3)中的任意一行,程序都會終止,我們不免產生疑問:爲什麼?synchronized會強制刷新主內存的變量值到線程棧??那麼sleep是幹嘛的?

      下面一起來解決代碼執行過程中產生的問題:

      Q1:爲什麼註釋代碼後程序不會終止?

      A1:因爲 boolean is=true 的變量值被前面線程(簡稱線程A)加載到自己的工作內存,在後面的線程(簡稱線程B)改變 boolean is=false 之後不一定會立馬寫入主存(不過這道題中應該會馬上寫入主存,因爲線程執行完 is=false之後線程就要退出了),即便立馬寫入了主存後線程A也不一定馬上load到工作內存中,所以程序一直不會終止?這個是我們大多數人想到的,但其實JVM針對現在的硬件水平已經做了很大程度的優化,基本上很大程度的保障了工作內存和主內存的及時同步,相當於默認使用了volatile。但只是最大程度!在CPU資源一直被佔用的時候,工作內存與主內存中間的同步,也就是變量的可見性就會不那麼及時!後面會驗證結論。

       Q2:爲什麼取消註釋中的任意一個代碼塊(1,2,3)程序就會終止??

       A2:行號爲1,2的代碼有一個共同點:都涉及到了synchronized同步鎖,那麼是否會像小編在文章開始時產生的質疑“synchronized會強制刷新主內存的變量值到線程棧”?或者說是sleep方法會刷新主存的變量值到線程棧嗎?

        事實上,我們都知道synchronized只會保證在同步塊中的變量的可變性,而is變量並不在該同步塊中,所以顯然不是這個導致的;那麼我們可以嘗試在“i++” 的代碼後面加上下面的代碼:

for(int k=0;k<100000;k++){
    new Object();
}
            這時,我們重新運行程序,程序會立刻終止,這又是爲什麼?

       因爲我們知道當CPU在被佔用的時候,數據的可見性得不到很好的保證。就像上面的例子中,沒有添加代碼之前,程序會一直循環做i++操作,所以CPU會被運算佔用;對於大量的new Object()操作來說,CPU已經不是主要站時間的操作,真正的耗時應該在內存的分配上(因爲CPU的處理速度明顯快過內存,不然也不會有CPU的寄存器了),所以CPU空閒後會遵循jvm優化基準,儘可能快的保證數據的可見性,從而從主存將is變量同步到工作內存中,最終導致程序的結束,這也就是sleep()方法雖然沒有涉及到同步操作,卻依然可以讓程序終止的原因,因爲sleep()方法會釋放CPU,但並不會釋放鎖;

後記

   ➷  知識擴展:

        volatile:此關鍵字保證了變量在線程的可見性,所有線程訪問由volatile修飾的變量,都必須從主存中讀取後操作,並在工作內存修改後立即寫回主存,保證了其他線程的可見性,同樣效果的關鍵字還有final。 

        synchronized:所有同步操作都必須保證 1、原子性 2、可見性,所以在同步塊中發生的變化會立馬寫回主存 

        sleep:此方法只會讓出CPU執行時間,並不會釋放鎖;

   ➷ 寄語   

        技術在於不斷的發現和總結,我們需要將自己輸入的知識進行整理後輸出,這樣才能對輸入的知識更好的理解;以上純屬個人理解,如有錯誤,請大家指正;

      

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章