成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

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

開發 后端
可見性是指一個線程對共享變量進行了修改,其他線程能夠立馬看到該共享變量更新后的值,這視乎是一個合情合理的要求,但是在多線程的情況下,可能就要讓你失望了,由于每個 CPU 都有自己的緩存,每個線程使用的可能是不同的 CPU ,這就會出現數據可見性的問題。

[[280728]]

 可見性問題

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

 

CUP 緩存與主內存的關系

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

  1. public class Demo { 
  2.  
  3.     private int count = 0; 
  4.  
  5.     // 每個線程為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 個程序對 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. 1  int i = 1; 
  2. 2  int m = 11; 
  3. 3  long x = 23L; 

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

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

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

雙重檢測鎖方案看上去非常完美,但是在實際運行時卻會出 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 對象的方法或者成員變量時將有可能觸發空指針異常。可能的執行流程圖:

未加 volatile 關鍵字的雙重檢測鎖單例模式

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

并發解決機制

 

責任編輯:華軒 來源: 平頭哥的技術博文
相關推薦

2020-07-24 08:51:54

Java編程語言開發

2014-02-18 13:45:39

bug程序員

2019-03-27 09:40:49

程序員技能開發者

2019-09-09 16:30:42

Redis架構數據庫

2020-07-07 12:30:16

Windows 10Windows操作系統

2021-01-05 06:42:23

JVM

2021-06-11 13:59:22

CSS原子類

2022-02-20 20:12:21

TypeScript

2023-03-26 20:23:10

Java開源工具

2021-09-30 16:22:34

程序員工具編程

2022-05-16 08:09:45

前端API

2023-11-30 08:19:52

偽類CSS

2015-03-25 09:55:34

程序員程序員修補BUG真正原因

2014-11-04 10:15:28

Android

2020-07-01 07:52:07

Java并發容器

2019-09-25 10:37:16

SpringBeanUtils接口

2015-04-17 10:12:58

光纖光纖衰減光纖損耗

2020-10-25 08:45:38

IPv6網絡協議網絡

2019-02-28 20:20:43

Python技巧編程語言

2025-04-09 08:25:00

JavaScript數組解構賦值
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 欧美一页 | 午夜精品久久久久久久久久久久 | 能看的av | 亚洲视频中文字幕 | 久久综合99| 91麻豆精品国产91久久久更新资源速度超快 | 久草视频2| 日韩一区二区福利 | 欧美精品一区二区三区在线 | 久久久不卡网国产精品一区 | 日韩国产在线观看 | 一区二区在线 | 91麻豆精品国产91久久久久久 | 欧美性网 | 超碰日本 | 欧美激情精品久久久久久 | 99精品欧美 | 久久亚洲欧美日韩精品专区 | 可以免费看的毛片 | 久久一区二区三区免费 | 亚洲情侣视频 | 中文字幕在线精品 | 夜夜久久| 欧美一级大片免费看 | 国产农村妇女毛片精品久久麻豆 | 亚洲一区二区精品视频 | 中文字幕高清av | 一区精品国产欧美在线 | 免费一区二区三区 | 久久久久久久久久爱 | 国产精品不卡一区 | 中文字幕一区二区三区精彩视频 | 毛片一区二区三区 | 精品日韩一区二区 | 日韩不卡在线 | 欧美久久天堂 | 国产精品国产自产拍高清 | 久久久久一区二区三区四区 | 国产www成人| 国精久久 | 欧美91|