日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
你的Java并發(fā)程序Bug,100%是這幾個(gè)原因造成的

 可見性問題

成都創(chuàng)新互聯(lián)公司總部坐落于成都市區(qū),致力網(wǎng)站建設(shè)服務(wù)有網(wǎng)站建設(shè)、成都做網(wǎng)站、網(wǎng)絡(luò)營銷策劃、網(wǎng)頁設(shè)計(jì)、網(wǎng)站維護(hù)、公眾號(hào)搭建、小程序定制開發(fā)、軟件開發(fā)等為企業(yè)提供一整套的信息化建設(shè)解決方案。創(chuàng)造真正意義上的網(wǎng)站建設(shè),為互聯(lián)網(wǎng)品牌在互動(dòng)行銷領(lǐng)域創(chuàng)造價(jià)值而不懈努力!

可見性是指一個(gè)線程對(duì)共享變量進(jìn)行了修改,其他線程能夠立馬看到該共享變量更新后的值,這視乎是一個(gè)合情合理的要求,但是在多線程的情況下,可能就要讓你失望了,由于每個(gè) CPU 都有自己的緩存,每個(gè)線程使用的可能是不同的 CPU ,這就會(huì)出現(xiàn)數(shù)據(jù)可見性的問題,先來看看下面這張圖:

CUP 緩存與主內(nèi)存的關(guān)系

對(duì)于一個(gè)共享變量 count ,每個(gè) CPU 緩存中都有一個(gè) count 副本,每個(gè)線程對(duì)共享變量 count 的操作的只能操作自己所在 CPU 緩存中的副本,不能直接操作主存或者其他 CPU 緩存中的副本,這也就產(chǎn)生了數(shù)據(jù)差異。由于可見性在多線程情況下造成程序問題的典型案例就是變量的累加,如下面這段程序:

 
 
 
 
  1. public class Demo { 
  2.  
  3.     private int count = 0; 
  4.  
  5.     // 每個(gè)線程為count + 10000 
  6.     public void add() { 
  7.         for (int i = 0; i < 10000; i++) { 
  8.             count += 1; 
  9.         } 
  10.     } 
  11.  
  12.     public static void main(String[] args) throws InterruptedException { 
  13.  
  14.         for (int i = 0; i < 10; i++) { 
  15.             Demo demo = new Demo(); 
  16.             Thread t1 = new Thread(() -> { 
  17.                 demo.add(); 
  18.             }); 
  19.             Thread t2 = new Thread(() -> { 
  20.                 demo.add(); 
  21.             }); 
  22.             t1.start(); 
  23.             t2.start(); 
  24.             t1.join(); 
  25.             t2.join(); 
  26.             System.out.println(demo.count); 
  27.         } 
  28.     } 

我們使用了 2 個(gè)程序?qū)?count 變量累加,每個(gè)線程累加 10000 次,按道理來說最終結(jié)果應(yīng)該是 20000 次,但是你多次執(zhí)行后,你會(huì)發(fā)現(xiàn)結(jié)果不一定是 20000 次,這就是由于共享變量的可見性造成的。

我們啟動(dòng)了兩個(gè)線程 t1 和 t2,線程啟動(dòng)的時(shí)候會(huì)把當(dāng)前主內(nèi)存的 count 讀入到自己的 CPU 緩存當(dāng)中,這時(shí)候 count 的值可能是 0 也可能是 1 或者其他,我們就默認(rèn)為 0,每個(gè)線程都會(huì)執(zhí)行 count += 1 操作,這是一個(gè)并行操作,CPU1 和 CPU2 緩存中的 count 都是 1,然后他們分別將自己緩存中的count 寫回到主內(nèi)存中,這時(shí)候主內(nèi)存中的 count 也是 1 ,并不是我們預(yù)計(jì)的 2,。這個(gè)原因就是數(shù)據(jù)可見性造成的。

原子性問題

原子性:即一個(gè)操作或者多個(gè)操作,要么全部執(zhí)行并且執(zhí)行的過程不會(huì)被任何因素打斷,要么就都不執(zhí)行。這個(gè)原子性針對(duì)的是 CPU 級(jí)別的,并不是我們 Java 代碼里面的原子性,拿我們可見性 Demo 程序中的 count += 1;命令為例,這一條 Java 命令最終會(huì)被編譯成如下三條 CPU 指令:

  • 把變量 count 從內(nèi)存加載到 CPU 的寄存器,假設(shè) count = 1
  • 在寄存器中執(zhí)行 count +1 操作,count = 1+1 =2
  • 將結(jié)果 +1 后的 count 寫入內(nèi)存

這是一個(gè)典型的 讀-改-寫 的操作,但是它不是原子性的,因?yàn)?多核CPU 之間有競(jìng)爭關(guān)系,并不是某一個(gè) CPU 一直執(zhí)行,他們會(huì)不斷的搶占執(zhí)行權(quán)、釋放執(zhí)行權(quán),所以上面三條指令就不一定是原子性的,下圖是兩個(gè)線程 count += 1命令的模擬流程:

非原子性操作

線程1 所在的 CPU 執(zhí)行完前兩條指令后,執(zhí)行權(quán)被 線程2 所在的 CPU 搶占了,這時(shí)候線程1 所在的 CPU 執(zhí)行掛起等待再次獲取執(zhí)行權(quán),線程2 所在的 CPU 獲取到執(zhí)行權(quán)之后,先從內(nèi)存中讀取 count,此時(shí)內(nèi)存中的 count 還是 1,線程2 所在的 CPU 恰好執(zhí)行完了這三條指令,線程2 執(zhí)行完之后內(nèi)存中的 count 就等于 2 了,這時(shí)候線程1 再次獲取了執(zhí)行權(quán),這時(shí)候線程1 只剩下最后一條將 count 寫回內(nèi)存的命令,執(zhí)行完之后,內(nèi)存中的 count 的值還是 2 ,并不是我們預(yù)計(jì)的 3。

有序性問題

有序性:程序執(zhí)行的順序按照代碼的先后順序執(zhí)行,比如下面這段代碼

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

按照有序性的話就需要按照代碼的順序執(zhí)行下來,但是執(zhí)行結(jié)果不一定是按照這個(gè)順序來的,因?yàn)?JVM 為了提高程序的運(yùn)行效率,會(huì)對(duì)上面的代碼按照 JVM 編譯器認(rèn)為較好的順序執(zhí)行,從而可能打亂代碼的執(zhí)行順序,是它會(huì)保證程序最終執(zhí)行結(jié)果和代碼順序執(zhí)行的結(jié)果是一致的,這也就是我們所說的指令重排序

由于指令重排序造成程序出 Bug 的典型案例就是:未加 volatile 關(guān)鍵字的雙重檢測(cè)鎖單例模式,如下代碼:

 
 
 
 
  1. public class Singleton { 
  2.     static Singleton instance; 
  3.     public static Singleton getInstance(){ 
  4.     // 第一次判斷 
  5.     if (instance == null) { 
  6.         // 加鎖,只有一個(gè)線程能夠獲取鎖 
  7.         synchronized(Singleton.class) { 
  8.             // 第二次判斷 
  9.             if (instance == null) 
  10.                 // 構(gòu)建對(duì)象,這里面就非常有學(xué)問了 
  11.                 instance = new Singleton(); 
  12.             } 
  13.     } 
  14.     return instance; 
  15.     } 

雙重檢測(cè)鎖方案看上去非常完美,但是在實(shí)際運(yùn)行時(shí)卻會(huì)出 Bug,會(huì)出現(xiàn)對(duì)象逸出的問題,可能會(huì)得到一個(gè)未構(gòu)建完的 Singleton 對(duì)象, 這個(gè)就是在構(gòu)建 Singleton 對(duì)象時(shí)指令重排序的問題。我們先來看看構(gòu)建對(duì)象理想型的操作指令:

  • 指令1:分配一塊內(nèi)存 M;
  • 指令2:在內(nèi)存 M 上初始化 Singleton 對(duì)象;
  • 指令3:然后 M 的地址賦值給 instance 變量。

但是實(shí)際在 JVM 編譯器上可能不是這樣,可能會(huì)被優(yōu)化成如下指令:

  • 指令1:分配一塊內(nèi)存 M;
  • 指令2:將 M 的地址賦值給 instance 變量;
  • 指令3:最后在內(nèi)存 M 上初始化 Singleton 對(duì)象。

看上去一個(gè)小小的優(yōu)化,也就是這么一個(gè)小小的優(yōu)化就會(huì)使你的程序不安全,假設(shè)搶到鎖的線程執(zhí)行完指令2 之后,此時(shí)的 instance 已經(jīng)不為空了,這時(shí)候來了線程C,線程C 看到的 instance 已經(jīng)是不為空的了,就會(huì)直接返回 instance 對(duì)象,這時(shí)候的 instance 并未初始化成功,調(diào)用 instance 對(duì)象的方法或者成員變量時(shí)將有可能觸發(fā)空指針異常。可能的執(zhí)行流程圖:

未加 volatile 關(guān)鍵字的雙重檢測(cè)鎖單例模式

上面就是造成 Java 程序在多線程情況下出 Bug 的三種原因,關(guān)于這些問題 JDK 公司也給出了相應(yīng)的解決辦法,具體如下圖所示,這些解決辦法的更多細(xì)節(jié),我們后面在細(xì)細(xì)道來。

并發(fā)解決機(jī)制


網(wǎng)站欄目:你的Java并發(fā)程序Bug,100%是這幾個(gè)原因造成的
網(wǎng)站地址:http://www.dlmjj.cn/article/djocspp.html