反骨之硬件&軟件爲Java併發編程中挖的坑(可見性&原子性&有序性)

前言

本篇博文,主要集中併發編程的三個問題:可見性、原子性、有序性。數據庫

只講理論,不談如何解決。編程

說白了併發編程中的三個問題,就是先輩們給咱們這些後輩留下來的坑!緩存


那麼,先輩們給併發編程帶來什麼坑?

相信大部分人都知道,一臺計算機的組成包括但不限於:CPU、內存、顯卡….安全

在這裏, 筆者將計算機中的組抽象成幾類,即:CPU、I/O設備、內存微信

有了以上三個分類,筆者接下來的文章就好寫了。(滑稽)多線程

  • Cpu多核緩存帶來的數據----可見性問題(硬件):(建議直接跳到小節末尾, 看結論便可)

    講道理,在處理速度方面CPU >> 內存 >> I/O設備併發

    這時候, 爲了充分發揮CPU的計算速度, 硬件方面則是在CPU和內存之間, 增長了一種叫高速緩存的技術。學習

    總而言之,高速緩存的出現主要是爲了解決CPU運算速度與內存讀寫速度不匹配的矛盾優化

    固然, 在單核時代Cpu緩存並無出現數據一致性問題, 可是在多核時代就不同了。操作系統

    企業微信20190810021224.png

    在多核處理器中,因爲Cpu並不與內存直接打交道, 而是經過高速緩存。

    同理, 內存也是並不直接與Cpu打交道,也是經過高速緩存與Cpu打交道。

    • cpu <——> 高速緩存 <———> 內存

    緩存一致性問題以及解決方案,網上一搜一大堆,筆者在此就不作過多的敘述。

    因此,只須要記住結論便可:

    Cpu緩存不一致爲併發編程帶來----可見性問題。(結論)


  • 操做系統中進程&線程上下文切換給併發編程帶來操做----原子性問題:

    在之前的印象中,說到原子性,筆者張口就是:原子操做是一個不可分割的總體, 該總體中的全部指令,要麼所有執行, 要麼所有不執行, 沒有中間狀態。在此,筆者所寫的原子性概念,彷彿過於寬泛和縹緲。並且,原子性在不一樣的使用場景,也有可能含義並不相同。

    接下來,咱們試着從數據庫事務併發編程兩個方面來進行對比:

    在數據庫中,原子性概念以下:

    • 事務被當作一個不可分割的總體,包含在其中的操做要麼所有執行,要麼所有不執行。且事務在執行過程當中若是發生錯誤,會被回滾到事務開始前的狀態,就像這個事務沒有執行同樣。

    在併發編程中,原子性概念以下:

    • 第一種理解:一個線程或進程在執行過程當中,沒有發生上下文切換
      • 上下文切換:指CPU從一個進程/線程切換到另一個進程/線程(切換的前提就是獲取CPU的使用權)。
    • 第二種理解:咱們把一個線程中的一個或多個操做(不可分割的總體),在CPU執行過程當中不被中斷的特性,稱爲原子性。(執行過程當中,一旦發生中斷,就會發生上下文切換)

    從上文中能夠看出,併發編程和數據庫二者之間的原子性概念有些類似。

    都是強調,一個原子操做不能被打斷!!

    因此,上下文切換給併發編程帶來——原子性問題。(結論)


  • 編譯器的編譯優化給併發編程帶來程序----有序性問題:

    講道理,在計算機語言中,高級語言的運行都須要經過編譯器轉換成機器語言,經過機器語言在計算機上運行。

    那麼,在編譯器中爲了提升運行速度,會對內存訪問的有關操做(讀&寫)作一種優化,即指令的重排序。這種重排序在單線程環境下,不影響結果的正確性。可是,在多線程環境下可能會對結果的正確性產生影響。

    指令的重排序,是形成併發編程的有序性問題的緣由。

    編譯器帶來指令重排序,而指令重排序形成有序性問題。

    編譯器優化—帶來—> 指令重排序—帶來—>有序性問題

    因此,編譯器的優化給併發編程帶來—有序性問題!

總結

  • 多核Cpu的高速緩存爲併發編程帶來—可見性問題。
  • 線程的切換給併發編程帶來—原子性問題。
  • 編譯器給併發編程帶來—有序性問題
  • 不論是可見、原子仍是有序性,都是線程安全問題的表現形式。

最後,本篇博文,主要是對剛學習併發編程的朋友,提出併發編程的三個問題的概念。

說實話,筆者剛開始學的時候,並無想過想過原子、有序、可見性的形成緣由,直接通通死記硬背!

有時候想一想,以爲本身挺好笑的。

總之,讀者們要記住,整個Java併發包的設計與實現,都是爲了解決併發編程的可見、原子、有序三個問題。

接下來,筆者會嘗試着寫寫,Java是如何解決可見性、原子性和有序性的….

相關文章
相關標籤/搜索