并發的前提條件
并發問題發生的前提條件一定是資源共享,這里的資源一般指的是數據,共享指的是多線程之間共享。
也就是只有在多線程共享資源的情況下才可能產生并發問題,這是并發問題產生的前提條件,在這個條件下,有可能產生并發問題,那么并發問題的根源究竟是什么呢?
CPU操作數據的基本機制
前面提到了并發的前提條件是數據共享,想了解并發問題的根源就需要知道CPU操作數據的基本原理;
數據存儲包括這幾個位置:磁盤、內存、緩存、寄存器;
寄存器可以認為是CPU的一部分,所以有的地方并沒有將CPU和寄存器拆分講解,通常來講只需要知道CPU運算時都是從寄存器取數據,運算完成后再放回寄存器即可,CPU和寄存器之間沒有其他任何中介。
緩存是CPU與寄存器之外的一層存儲,但也是每一個CPU獨立占有的一塊內存區域,各個CPU緩存之間數據不可以共享。
內存是程序運行時數據的主要存放區域,內存數據是共享的,一般來講,各個CPU都可以訪問內存中的數據;
磁盤,數據最終持久化的存儲;
CPU操作數據的流程一般是先由磁盤讀到內存,再從內存讀到緩存,再由緩存到寄存器進行運算;運算之后的結果直接寫入寄存器,然后刷新到緩存,再刷新到內存,最后寫入磁盤;
程序數據流圖
并發問題的源頭
了解了CPU運行機制之后,下面說并發問題的根源,主要是由于數據可見性、操作原子性、操作有序性這三個原因導致的;
什么是數據可見性?
通俗點來說就是CPU看到的數據并不是最新的數據,CPU讀取數據是優先從緩存中讀取,如果緩存中存在就使用緩存中的數據,假如數據被另一個CPU改變了,這時其他CPU中緩存數據就可能與內存中的數據不一致,也就是CPU沒有看到并使用最新的數據,導致程序執行結果異常。
什么是操作原子性?
同一個CPU可以交替執行多個線程,不太了解的讀者可以初步學習一下CPU時間片與線程調度的基本知識。
在同一個CPU,交替執行多個線程的時候,就可能出現線程中斷,并且在中斷過程中受其他線程影響而導致中斷的線程恢復之后,執行邏輯異常。
比如:a線程執行count = count + 1操作,b線程也執行相同的操作;當a線程讀取到count的值,并進行加1計算之后,還沒寫回到內存之前被中斷,b線程完全執行了count = count + 1,count的值得到更新;這時a線程恢復(并不會重新讀取并計算),將之前計算的值寫回到緩存,導致count本來應該執行兩次加1,但最終結果只加了一次1;
什么是操作有序性?
有序性指的是CPU執行代碼的順序和程序開發者定義的順序不一致?為什么還會不一致呢?
編譯器在將高級開發語言編譯成計算機指令的時候,出于性能優化,可能會對代碼執行重排序,CPU在執行指令的時候,也可能對代碼重排序;當然重排序的前提是在單線程條件下的語義不變性,但不能保證多線程條件下語義也相同。
Java單例模式中的雙重校驗鎖,單例變量為什么要聲明為volatile,就是為了解決指令重排序帶來的問題,我們在下一章節進行詳細講解。感興趣的也可以自行查閱資料學習。
并發問題的解決方案
并發問題不是Java語言特有的,而是計算機運行原理與操作系統帶來的,那么從計算機與操作系統層面來看,它們都提供了哪些解決方案來避免數據可見性、程序原子性、操作有序性的保障呢?Java語言又是如何對這些方案進行封裝的呢?開發者有哪些手段可以解決這些問題呢?
-
JAVA
+關注
關注
19文章
2970瀏覽量
104800 -
線程
+關注
關注
0文章
505瀏覽量
19697 -
進程
+關注
關注
0文章
203瀏覽量
13962
發布評論請先 登錄
相關推薦
評論