1. 程式人生 > >你的 Java 併發程式 Bug,100% 是這幾個原因造成的

你的 Java 併發程式 Bug,100% 是這幾個原因造成的

可見性問題

可見性是指一個執行緒對共享變數進行了修改,其他執行緒能夠立馬看到該共享變數更新後的值,這視乎是一個合情合理的要求,但是在多執行緒的情況下,可能就要讓你失望了,由於每個 CPU 都有自己的快取,每個執行緒使用的可能是不同的 CPU ,這就會出現資料可見性的問題,先來看看下面這張圖:

對於一個共享變數 count ,每個 CPU 快取中都有一個 count 副本,每個執行緒對共享變數 count 的操作的只能操作自己所在 CPU 快取中的副本,不能直接操作主存或者其他 CPU 快取中的副本,這也就產生了資料差異。由於可見性在多執行緒情況下造成程式問題的典型案例就是變數的累加,如下面這段程式:

public class Demo {

    private int count = 0;

    // 每個執行緒為count + 10000
    public void add() {
        for (int i = 0; i < 10000; i++) {
            count += 1;
        }
    }

    public static void main(String[] args) throws InterruptedException {

        for (int i = 0; i < 10; i++) {
            Demo demo = new Demo();
            Thread t1 = new Thread(() -> {
                demo.add();
            });
            Thread t2 = new Thread(() -> {
                demo.add();
            });
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            System.out.println(demo.count);
        }
    }
}

我們使用了 2 個程式對 count 變數累加,每個執行緒累加 10000 次,按道理來說最終結果應該是 20000 次,但是你多次執行後,你會發現結果不一定是 20000 次,這就是由於共享變數的可見性造成的。

我們啟動了兩個執行緒 t1 和 t2,執行緒啟動的時候會把當前主記憶體的 count 讀入到自己的 CPU 快取當中,這時候 count 的值可能是 0 也可能是 1 或者其他,我們就預設為 0,每個執行緒都會執行 count += 1 操作,這是一個並行操作,CPU1 和 CPU2 快取中的 count 都是 1,然後他們分別將自己快取中的count 寫回到主記憶體中,這時候主記憶體中的 count 也是 1 ,並不是我們預計的 2,。這個原因就是資料可見性造成的。

原子性問題

原子性:即一個操作或者多個操作,要麼全部執行並且執行的過程不會被任何因素打斷,要麼就都不執行。這個原子性針對的是 CPU 級別的,並不是我們 Java 程式碼裡面的原子性,拿我們可見性 Demo 程式中的 count += 1;命令為例,這一條 Java 命令最終會被編譯成如下三條 CPU 指令:

  • 把變數 count 從記憶體載入到 CPU 的暫存器,假設 count = 1
  • 在暫存器中執行 count +1 操作,count = 1+1 =2
  • 將結果 +1 後的 count 寫入記憶體

這是一個典型的 讀-改-寫 的操作,但是它不是原子性的,因為 多核CPU 之間有競爭關係,並不是某一個 CPU 一直執行,他們會不斷的搶佔執行權、釋放執行權,所以上面三條指令就不一定是原子性的,下圖是兩個執行緒 count += 1命令的模擬流程:

執行緒1 所在的 CPU 執行完前兩條指令後,執行權被 執行緒2 所在的 CPU 搶佔了,這時候執行緒1 所在的 CPU 執行掛起等待再次獲取執行權,執行緒2 所在的 CPU 獲取到執行權之後,先從記憶體中讀取 count,此時記憶體中的 count 還是 1,執行緒2 所在的 CPU 恰好執行完了這三條指令,執行緒2 執行完之後記憶體中的 count 就等於 2 了,這時候執行緒1 再次獲取了執行權,這時候執行緒1 只剩下最後一條將 count 寫回記憶體的命令,執行完之後,記憶體中的 count 的值還是 2 ,並不是我們預計的 3。

有序性問題

有序性:程式執行的順序按照程式碼的先後順序執行,比如下面這段程式碼

1  int i = 1;
2  int m = 11;
3  long x = 23L;

按照有序性的話就需要按照程式碼的順序執行下來,但是執行結果不一定是按照這個順序來的,因為 JVM 為了提高程式的執行效率,會對上面的程式碼按照 JVM 編譯器認為最優的順序執行,從而可能打亂程式碼的執行順序,是它會保證程式最終執行結果和程式碼順序執行的結果是一致的,這也就是我們所說的指令重排序

由於指令重排序造成程式出 Bug 的典型案例就是:未加 volatile 關鍵字的雙重檢測鎖單例模式,如下程式碼:

public class Singleton { 
    static Singleton instance; 
    public static Singleton getInstance(){ 
    // 第一次判斷
    if (instance == null) { 
        // 加鎖,只有一個執行緒能夠獲取鎖
        synchronized(Singleton.class) { 
            // 第二次判斷
            if (instance == null) 
                // 構建物件,這裡面就非常有學問了
                instance = new Singleton(); 
            } 
    }
    return instance; 
    } 
}

雙重檢測鎖方案看上去非常完美,但是在實際執行時卻會出 Bug,會出現物件逸出的問題,可能會得到一個未構建完的 Singleton 物件, 這個就是在構建 Singleton 物件時指令重排序的問題。我們先來看看構建物件理想型的操作指令:

  • 指令1:分配一塊記憶體 M;
  • 指令2:在記憶體 M 上初始化 Singleton 物件;
  • 指令3:然後 M 的地址賦值給 instance 變數。

但是實際在 JVM 編譯器上可能不是這樣,可能會被優化成如下指令:

  • 指令1:分配一塊記憶體 M;
  • 指令2:將 M 的地址賦值給 instance 變數;
  • 指令3:最後在記憶體 M 上初始化 Singleton 物件。

看上去一個小小的優化,也就是這麼一個小小的優化就會使你的程式不安全,假設搶到鎖的執行緒執行完指令2 之後,此時的 instance 已經不為空了,這時候來了執行緒C,執行緒C 看到的 instance 已經是不為空的了,就會直接返回 instance 物件,這時候的 instance 並未初始化成功,呼叫 instance 物件的方法或者成員變數時將有可能觸發空指標異常。可能的執行流程圖:

上面就是造成 Java 程式在多執行緒情況下出 Bug 的三種原因,關於這些問題 JDK 公司也給出了相應的解決辦法,具體如下圖所示,這些解決辦法的更多細節,我們後面在細細道來。

文章不足之處,望大家多多指點,共同學習,共同進步

最後

打個小廣告,歡迎掃碼關注微信公眾號:「平頭哥的技術博文」,一起進步吧。

相關推薦

Java 併發程式 Bug100% 是原因造成

可見性問題 可見性是指一個執行緒對共享變數進行了修改,其他執行緒能夠立馬看到該共享變數更新後的值,這視乎是一個合情合理的要求,但是在多執行緒的情況下,可能就要讓你失望了,由於每個 CPU 都有自己的快取,每個執行緒使用的可能是不同的 CPU ,這就會出現資料可見性的問題,先來看看下面這張圖: 對於一個共享

成為Java頂尖程式設計師 9本書就夠了

“學習的最好途徑就是看書”,這是我自己學習並且小有了一定的積累之後的第一體會。個人認為看書有兩點好處: 1.能出版出來的書一定是經過反覆的思考、雕琢和稽核的,因此從專業性的角度來說,一本好書的價值遠超其他資料 2.對著書上的程式碼自己敲的時候方便 “看

書單推薦:成為Java頂尖程式設計師 11本書就夠了

“學習的最好途徑就是看書“,這是我自己學習並且小有了一定的積累之後的第一體會。個人認為看書有兩點好處: 1.能出版出來的書一定是經過反覆的思考、雕琢和稽核的,因此從專業性的角度來說,一本好書的價值遠超其他資料 2.對著書上的程式碼自己敲的時候方便 “看完書之後再次提升

成為Java頂尖程式設計師 10本書就夠了

“學習的最好途徑就是看書“,這是我自己學習並且小有了一定的積累之後的第一體會。個人認為看書有兩點好處:   1.能出版出來的書一定是經過反覆的思考、雕琢和稽核的,因此從專業性的角度來說,一本好書的價值遠超其他資料   2.對著書上的程式碼自己敲的時候方便   “看完書之後

成為Java頂尖程式設計師 11本書就夠了

學習的最好途徑就是看書“,這是我自己學習並且小有了一定的積累之後的第一體會。個人認為看書有兩點好處: 1. 能出版出來的書一定是經過反覆的思考、雕琢和稽核的,因此從專業性的角度來說,一本好書的價值遠超其他資料 2. 對著書上的程式碼自己敲的時候方便 “

成為Java頂尖程式設計師11本書就夠了

來源:程式設計師之家學習的最好途徑就是看書“,這是我自己學習並且小有了一定的積累之後的第一體會。

成為java頂級程式設計師11本書

學習的最好途徑就是看書“,這是我自己學習並且小有了一定的積累之後的第一體會。個人認為看書有兩點好處:1.能出版出來的書一定是經過反覆的思考、雕琢和稽核的,因此從專業性的角度來說,一本好書的價值遠超其他資料2.對著書上的程式碼自己敲的時候方便“看完書之後再次

成為Java頂尖程式設計師 11本書就夠了及十必備的技術網站

"學習的最好途徑就是看書",這是我自己學習並且小有了一定的積累之後的第一體會。個人認為看書有兩點好處: 能出版出來的書一定是經過反覆的思考、雕琢和稽核的,因此從專業性的角度來說,一本好書的價值遠超其他資料; 對著書上的程式碼自己敲的時候方便。 "看完書之後再

想要搭建論壇?Guide哥調研了100 Java 開源論壇系統發現 5 最好用!

[大家好!我是 Guide 哥,Java 後端開發。一個會一點前端,喜歡烹飪的自由少年。](https://www.yuque.com/docs/share/71251673-1fef-416e-93d7-489a25a9eda5?#%20%E3%80%8A%E8%B5%B0%E8%BF%91JavaGuid

菜鳥們通過樣例學會 uptime 命令的使用方法 | Linux 中國

望名生義。uptime 命令告訴你係統啟動了(執行了)多長時間。這是語法:-- Himanshu Arora 本文導航◈ Linux uptime 命令 09%◈ Q1、怎樣使用 uptime 命令 22%◈ Q2、怎樣以更人

決定一個程式設計師能走多遠的不是年齡而是原因

都說程式設計師有“中年危機”,說是程式設計師到了30-40歲就開始不吃香了,甚至會被企業淘汰。那麼事實真的是如此嗎?建議大家不妨看看那些程式設計師大腕,哪一個不是50歲上下的。 當然程式設計師是個很燒腦的工作,對自身技術要求很高,如果你到了中年而技術水平還停留在初級階段,那你被淘汰也沒什麼抱怨的

如何巧妙的拒絕別人方法保證拒絕他們還會感激

如果在人際交往中,你需要拒絕人時,不妨用下列方法試一試:   不要立刻就拒絕:立刻拒絕,會讓人覺得你是一個冷漠無情的人,甚至覺得你對他有成見。   不要輕易地拒絕:有時候輕易地拒絕別人,會失去許多幫助別人、獲得友誼的機會。   不要在盛怒下拒絕:

php類中的$thisstaticconstself關鍵字使用方法

ons all 丟失 static ret 方法名 style sin 靜態方法 本篇文章主要分享一下關於php類中的$this,static,final,const,self這幾個關鍵字使用方法 $this $this表示當前實例,在類的內部方法訪問未聲明為const及s

為什麽別人的工作效率總是比快?原來是掌握了Excel技巧

col 大小 別人 技巧 size watermark ado 高效 ade 掌握一些最基本的Excel技巧可以有效地提高工作效率。下面給大家分享4個實用的Excel的技巧,能夠幫你在工作上節省大量的時間! 快速整理表格中的數據 選中單元格數據,按“Ctrl+1”快捷鍵彈出

為什麼別人的工作效率總是比快?原來是掌握了Excel技巧

掌握一些最基本的Excel技巧可以有效地提高工作效率。下面給大家分享4個實用的Excel的技巧,能夠幫你在工作上節省大量的時間! 快速整理表格中的資料 選中單元格資料,按“Ctrl+1”快捷鍵彈出設定單元格格式框,點選“自定義”,將型別更改為“0!,0,”即可。 具體操作如下:

5實力超群的手機APP足夠用了(系統類、閱讀類)

手機是大家都離不開的工具之一,那麼你的手機中又有幾款手機稱心如意的了?現在隨著經濟的發展,市場上的APP也大量的被研發出來,在眾多的手機APP中,我們挑的眼花繚亂,不知道哪款實用,這就是典型的選擇困難症。 在眾多的手機APP中,小編覺得以下這幾款使用起來是很不錯的,若是你的

雙十一秒殺系統架構設計關鍵點!

話說馬上要到雙11了,就來談談如何設計一個秒殺系統架構 技術挑戰 1. 對原有業務形成衝擊 秒殺活動只是網站營銷的一個附加活動,特點是:時間短、併發訪問量大,如果和網站原有應用部署在一起,必然會對現有業務造成衝擊。 解決方案:將秒殺系統獨立部署,甚至使用獨立域名,

關於web開發中遇到的hrefurl src屬性的區別個人小結

      href:Hypertext Reference的縮寫。意思是超文字引用。 常見到的元素:<a href=""> <link href=""/> src: 在HTML語言中,網頁中插入圖片所用標籤<img>, <img&

初學Web前端開發學會必殺技薪資爆表!

曾經的我怎麼也想不到,web前端開領域能發展到今天的樣子,但是對於很多想轉行學習的初學者,你首先需要先掌握必備的基礎知識,以及獨立

來阿里有段時間了談談月最真實的感受

作者:黃小斜 文章來源:微信公眾號【程式設計師江湖】   閱讀本文大概需要 4 分鐘。   ​   有段時間沒寫過原創了,想了各種理由,發現其實理由就一個,沒時間。   我來阿里,已經幾個月了。這段時間,最大的感受就是累。我是在今年的四月份加入阿里