Java多線程--原子性、可見性、有序性

計算機的內存模型:

  計算機在運行行程序的時候,指令由CPU執行,計算機上數據存放在物理內存當中,CPU在執行指令的時候免不了要和數據打交道。剛開始,還相安無事的,可是隨着CPU技術的發展,CPU的執行速度愈來愈快。而因爲內存的技術並無太大的變化,因此從內存中讀取和寫入數據的過程和CPU的執行速度比起來差距就會愈來愈大,這就致使CPU每次操做內存都要耗費不少等待時間,但是總不能讓內存成爲計算機處理的瓶頸,因此,人們想出來了一個好的辦法,就是在CPU和內存之間增長高速緩存,就是保存一份數據拷貝。他的特色是速度快,內存小,而且昂貴。程序員

  程序的執行過程就變成程序運行過程時,會將運算須要的數據從主存複製一份到CPU的高速緩存當中,CPU進行計算時就能夠直接從它的高速緩存讀取數據和向其中寫入數據,當運算結束以後,再將高速緩存中的數據刷新到主存當中。編程

  而隨着CPU能力的不斷提高,一層緩存就慢慢的沒法知足要求了,就逐漸的衍生出多級緩存。按照數據讀取順序和與CPU結合的緊密程度,CPU緩存能夠分爲一級緩存(L1),二級緩存(L3),部分高端CPU還具備三級緩存(L3),每一級緩存中所儲存的所有數據都是下一級緩存的一部分。這三種緩存的技術難度和制形成本是相對遞減的,因此其容量也是相對遞增的。在有了多級緩存以後,程序的執行就變成了當CPU要讀取一個數據時,首先從一級緩存中查找,若是沒有找到再從二級緩存中查找,若是仍是沒有就從三級緩存或內存中查找。緩存

  單核CPU只含有一套L1,L2,L3緩存;若是CPU含有多個核心,即多核CPU,則每一個核心都含有一套L1(甚至和L2)緩存,而共享L2(或者和L3)緩存。隨着計算機能力不斷提高,開始支持多線程。咱們分別來分析下單線程、多線程在單核CPU、多核CPU中的影響:多線程

  單線程:CPU核心的緩存只被一個線程訪問。緩存獨佔,不會出現訪問衝突等問題。併發

  單核CPU,多線程:進程中的多個線程會同時訪問進程中的共享數據,CPU將某塊內存加載到緩存後,不一樣線程在訪問相同的物理地址的時候,都會映射到相同的緩存位置,這樣即便發生線程的切換,緩存仍然不會失效。但因爲任什麼時候刻只能有一個線程在執行,所以不會出現緩存訪問衝突。編程語言

  多核CPU,多線程:每一個核都至少有一個L1 緩存。多個線程訪問進程中的某個共享內存,且這多個線程分別在不一樣的核心上執行,則每一個核心都會在各自的緩存中保留一份共享內存的緩衝。因爲多核是能夠並行的,可能會出現多個線程同時寫各自的緩存的狀況,而各自的緩存之間的數據就有可能不一樣。性能

處理器優化和指令重排

  除了CPU與內存之間添加緩存致使的緩存一致性問題以外,還有一種硬件問題,那就是爲了使處理器內部的運算單元可以儘可能的被充分利用,處理器可能會對輸入代碼進行亂序執行處理,這就是處理器優化。除了如今不少流行的處理器會對代碼進行優化亂序處理,不少編程語言的編譯器也會有相似的優化,好比Java虛擬機的即時編譯器(JIT)也會作指令重排。可想而知,若是任由處理器優化和編譯器對指令重排的話,就可能致使各類各樣的問題。優化

內存模型

  緩存一致性問題、處理器器優化的指令重排問題是硬件的不斷升級致使的。那麼,有沒有什麼機制能夠很好的解決上面的這些問題呢?最簡單直接的作法就是廢除處理器和處理器的優化技術、廢除CPU緩存,讓CPU直接和主存交互。可是,這麼作雖然能夠保證多線程下的併發問題。可是,這就有點因噎廢食了。因此,爲了保證併發編程中能夠知足原子性、可見性及有序性。有一個重要的概念,那就是——內存模型。操作系統

  原子性問題、可見性問題和有序性問題,是人們抽象定義出來的。而這個抽象的底層問題就是前面提到的緩存一致性問題、處理器優化問題和指令重排問題等。原子性是指在一個操做中就是cpu不能夠在中途暫停而後再調度,既不被中斷操做,要不執行完成,要不就不執行。可見性是指當多個線程訪問同一個變量時,一個線程修改了這個變量的值,其餘線程可以當即看獲得修改的值。有序性即程序執行的順序按照代碼的前後順序執行。線程

  爲了保證共享內存的正確性(可見性、有序性、原子性),內存模型定義了共享內存系統中多線程程序讀寫操做行爲的規範。經過這些規則來規範對內存的讀寫操做,從而保證指令執行的正確性。它與處理器有關、與緩存有關、與併發有關、與編譯器也有關。它解決了CPU多級緩存、處理器優化、指令重排等致使的內存訪問問題,保證了併發場景下的可見性、原子性和有序性。內存模型解決併發問題主要採用兩種方式:限制處理器優化和使用內存屏障。

Java內存模型

  內存模型,這是解決多線程場景下併發問題的一個重要規範,那麼它具體的實現是如何的呢,不一樣的編程語言,在實現上可能有所不一樣。咱們知道,Java程序是須要運行在Java虛擬機上面的,Java內存模型(Java Memory Model ,JMM)就是一種符合內存模型規範的,屏蔽了各類硬件和操做系統的訪問差別的,保證了Java程序在各類平臺下對內存的訪問都能保證效果一致的機制及規範。提到Java內存模型,通常指的是JDK 5 開始使用的新的內存模型。

  Java內存模型規定了全部的變量都存儲在主內存中,每條線程還有本身的工做內存,線程的工做內存中保存了該線程中是用到的變量的主內存副本拷貝,線程對變量的全部操做都必須在工做內存中進行,而不能直接讀寫主內存。不一樣的線程之間也沒法直接訪問對方工做內存中的變量,線程間變量的傳遞均須要本身的工做內存和主存之間進行數據同步進行。而JMM就做用於工做內存和主存之間數據同步過程,它規定了如何作數據同步以及何時作數據同步。總結下,JMM是一種規範,目的是解決因爲多線程經過共享內存進行通訊時,存在的本地內存數據不一致、編譯器會對代碼指令重排序、處理器會對代碼亂序執行等帶來的問題。

Java內存模型的實現

  Java中提供了一系列和併發處理相關的關鍵字,好比volatilesynchronizedfinalconcurren包等。其實這些就是Java內存模型封裝了底層的實現後提供給程序員使用的一些關鍵字,在開發多線程的代碼的時候,咱們能夠直接使用synchronized等關鍵字來控制併發,歷來就不須要關心底層的編譯器優化、緩存一致性等問題。因此,Java內存模型,除了定義了一套規範,還提供了一系列原語,封裝了底層實現後,供開發者直接使用。

原子性

在Java中,爲了保證原子性,提供了兩個高級的字節碼指令monitorentermonitorexit。這兩個字節碼,在Java中對應的關鍵字就是synchronized。所以,在Java中可使用synchronized來保證方法和代碼塊內的操做是原子性的。

可見性

Java內存模型是經過在變量修改後將新值同步回主內存,在變量讀取前從主內存刷新變量值的這種依賴主內存做爲傳遞媒介的方式來實現的,Java中的volatile關鍵字提供了一個功能,那就是被其修飾的變量在被修改後能夠當即同步到主內存,被其修飾的變量在每次是用以前都從主內存刷新。所以,可使用volatile來保證多線程操做時變量的可見性,除了volatile,Java中的synchronizedfinal兩個關鍵字也能夠實現可見性。只不過實現方式不一樣。

有序性

在Java中,可使用synchronizedvolatile來保證多線程之間操做的有序性。實現方式有所區別:volatile關鍵字會禁止指令重排。synchronized關鍵字保證同一時刻只容許一條線程操做。

介紹完了Java併發編程中解決原子性、可見性以及有序性可使用的關鍵字。咱們發現,好像synchronized關鍵字是萬能的,他能夠同時知足以上三種特性,這其實也是不少人濫用synchronized的緣由,可是synchronized是比較影響性能的,雖然編譯器提供了不少鎖優化技術,可是也不建議過分使用。

相關文章
相關標籤/搜索