引言
并發問題一直是Java領域的高階問題,要想掌握它不僅需要了解JVM的記憶體模型,更需要對計算機底層硬體有深入的理解,本文主要探討下Java并發安全問題的根源,
并發安全問題分析
計算機記憶體模型
我們都知道程式猿撰寫的代碼都是跑在具體的硬體架構上面的,只是目前的高級語言系統屏蔽了很多底層硬體細節,但是如果想要對于并發問題有深入的理解,還是需要對底層計算機硬體系統的細節有更多的了解,因此要想分析并發安全問題的根本原因,我們需要從問題現象出發,刨根問底,深入研究才能找到問題的答案,
首先從計算機硬體系統出發,我們可以將計算機系統簡化為三大部件,即CPU、記憶體以及IO設備,隨著技術的不斷發展,計算機硬體也有了長足的發展,各大部件的能力與日俱增,但是有一個問題一直圍繞在計算機硬體結構周圍,那就是CPU、記憶體以及IO設備之間的資料訪問速度有著巨大的速度差異,正式由于這種訪問速度的巨大差異造成了影響程式性能的最大因素正是最慢的IO設備,因此如果需要提升整體的性能,僅僅提高某一項是不夠的,要從整體出發,充分發揮CPU性能優勢,

為了應對這種資料讀取速率差異,CPU 中增加了高速快取,來平衡其與記憶體的速度差異,作業系統通過增加行程、執行緒,以便與最大可能分時復用 CPU,充分挖掘CPU性能,
在CPU中都會有一個高速緩沖區,在實際運行程序中,CPU首先從計算機主存中將資料復制到高速緩沖區中,CPU在進行運算時,直接基于高速緩沖區的資料進行運算,邏輯運算之后,再將高速緩沖區的資料重繪到主存中,通過這樣的方式,CPU的執行指令的速度就可以大大提升,
在單核CPU時代,程式中所有的執行緒都跑在這顆獨苗 CPU 中,由于所有當執行緒都是操作同一塊 CPU 的快取,因此據一個執行緒對快取的操作,對另外一個執行緒來說一定是可見的,因此不存在執行緒安全的問題,

但是當在多核CPU場景下,執行緒跑在不同當CPU中,因此對變數進行邏輯操作時,對其他執行緒不可見,因此會存在并發安全問題,

到此,我們分析出了并發安全的第一個根源,即快取導致的資料可見性問題,由于存在CPU高速快取,不同執行緒所在不同的CPU在計算后結果互不可見,這才導致了并發安全問題,
JVM記憶體模型
JVM定義的記憶體模型實際是計算機硬體架構在JVM中的映射體現,記憶體模型屏蔽了不同作業系統與記憶體硬體的訪問差異,Java的記憶體模型如圖3所示:

JVM啟動運行之后,作業系統邊會為該JVM行程分配制定的的記憶體空間,這部分記憶體空間即為上圖中的主記憶體,實際我們的Java程式的所有作業都由執行緒來完成,而每個執行緒都會有一小塊記憶體,即所謂的作業記憶體,Java中的執行緒在執行的程序中,會先將資料從主記憶體中復制到執行緒的作業記憶體,然后再執行計算,執行計算之后,再把計算結果重繪到主記憶體中,

我們一起來分析下count++在多執行緒場景下無法得到預期結果的原因,

對于count++的操作 看上去是執行了一條指令實際上包含了三條指令,
(1)首先,需要把變數 count 從記憶體加載到作業執行緒的作業記憶體中;
(2)加載后在作業記憶體中執行 +1 操作;
(3)最后,將計算結果寫入記憶體,
如上文所說的,由于計算機個大部件之間存在資料處理速度差異,處理一項任務時往往是CPU在等待其他部件完成后才進行后續的操作,為了提高CPU的作業效率,可以在CPU等待期間讓出CPU使用權,讓CPU去處理其他事情,這就是所謂的分時復用,那么在Java多執行緒場景下,必定也會發生執行緒切換,如下圖所示,由于count++不具備運算的原子性,導致了執行緒在運行程序中發生執行緒切換,最終導致輸出結果與預期不一致,

我們把一個或者多個操作在 CPU 執行的程序中不被中斷的特性稱為原子性,如上面的例子,如果保證了count++的原子特性,那么就不會有并發安全問題了,
總結
本文從計算機記憶體模型出發,再到JVM記憶體,分析了Java并發安全問題根本原因分別是多執行緒下的資料可可見性以及執行緒切換帶來的原子性問題,那么這些問題應該怎么解決呢?在下一篇文章中,我們再繼續探討,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/287325.html
標籤:其他
上一篇:php反序列化學習筆記5
