Java內存模型

Question:在併發編程中,多個線程之間採起什麼機制進行通訊(信息交換),什麼機制進行數據的同步? 
Answer:在Java語言中,採用的是共享內存模型來實現多線程之間的信息交換和數據同步的。程序員

線程之間經過共享程序公共的狀態,經過讀-寫內存中公共狀態的方式來進行隱式的通訊。同步指的是程序在控制多個線程之間執行程序的相對順序的機制,在共享內存模型中,同步是顯式的,程序員必須顯式指定某個方法/代碼塊須要在多線程之間互斥執行。編程

 

概述數組

Java內存模型的主要目標是定義程序中各個變量的訪問規則,即在JVM中將變量存儲到內存和從內存中取出變量這樣的底層細節。此處的變量與Java編程裏面的變量有所不一樣,它包含了實例字段、靜態字段和構成數組對象的元素,但不包含局部變量和方法參數,由於後者是線程私有的,不會共享,固然不存在數據競爭問題(若是局部變量是一個reference引用類型,它引用的對象在Java堆中可被各個線程共享,可是reference引用自己在Java棧的局部變量表中,是線程私有的)。爲了得到較高的執行效能,Java內存模型並無限制執行引擎使用處理器的特定寄存器或者緩存來和主內存進行交互,也沒有限制即時編譯器進行調整代碼執行順序這類優化措施。緩存

JMM規定了全部的變量都存儲在主內存(Main Memory)中。每一個線程還有本身的工做內存(Working Memory),線程的工做內存中保存了該線程使用到的變量的主內存的副本拷貝,線程對變量的全部操做(讀取、賦值等)都必須在工做內存中進行,而不能直接讀寫主內存中的變量(volatile變量仍然有工做內存的拷貝,可是因爲它特殊的操做順序性規定,因此看起來如同直接在主內存中讀寫訪問通常)。不一樣的線程之間也沒法直接訪問對方工做內存中的變量,線程之間值的傳遞都須要經過主內存來完成。 安全

舉例:線程1和線程2要想進行數據的交換通常要經歷下面的步驟: 
(1)線程1把工做內存1中的更新過的共享變量刷新到主內存中去。 
(2)線程2到主內存中去讀取線程1刷新過的共享變量,而後copy一份到工做內存2中去。多線程

 

內存模型的特性併發

Java內存模型是圍繞着併發編程中原子性、可見性、有序性這三個特徵來創建的。app

  原子性函數

  原子性是指一個操做不能被打斷,要麼所有執行完畢,要麼不執行。在這點上有點相似於事務操做,要麼所有執行成功,要麼回退到執行該操做以前的狀態。基本類型數據的訪問大都是原子操做,long 和double類型的變量是64位,可是在32位JVM中,32位的JVM會將64位數據的讀寫操做分爲2次32位的讀寫操做來進行,這就致使了long、double類型的變量在32位虛擬機中是非原子操做,數據有可能會被破壞,也就意味着多個線程在併發訪問的時候是線程非安全的。優化

  可見性

  一個線程對共享變量作了修改以後,其餘的線程當即可以看到(感知到)該變量這種修改(變化)。   

  Java內存模型是經過將在工做內存中的變量修改後的值同步到主內存,在讀取變量前從主內存刷新最新值到工做內存中,這種依賴主內存的方式來實現可見性的。不管是普通變量仍是volatile變量都是如此,區別在於:volatile的特殊規則保證了volatile變量值修改後的新值馬上同步到主內存,每次使用volatile變量前當即從主內存中刷新,所以volatile保證了多線程之間的操做變量的可見性,而普通變量則不能保證這一點。

  除了volatile關鍵字能實現可見性以外,還有synchronized,Lock,final也是能夠的。

  使用synchronized關鍵字,在同步方法/同步塊開始時(Monitor Enter),使用共享變量時會從主內存中刷新變量值到工做內存中(即從主內存中讀取最新值到線程私有的工做內存中),在同步方法/同步塊結束時(Monitor Exit),會將工做內存中的變量值同步到主內存中去(即將線程私有的工做內存中的值寫入到主內存進行同步)。

  使用Lock接口的最經常使用的實現ReentrantLock(重入鎖)來實現可見性:當咱們在方法的開始位置執行lock.lock()方法,這和synchronized開始位置(Monitor Enter)有相同的語義,即便用共享變量時會從主內存中刷新變量值到工做內存中(即從主內存中讀取最新值到線程私有的工做內存中),在方法的最後finally塊裏執行lock.unlock()方法,和synchronized結束位置(Monitor Exit)有相同的語義,即會將工做內存中的變量值同步到主內存中去(即將線程私有的工做內存中的值寫入到主內存進行同步)。

  final關鍵字的可見性是指:被final修飾的變量,在構造函數數一旦初始化完成,而且在構造函數中並無把「this」的引用傳遞出去(「this」引用逃逸是很危險的,其餘的線程極可能經過該引用訪問到只「初始化一半」的對象),那麼其餘線程就能夠看到final變量的值。

  有序性

  對於一個線程的代碼而言,咱們老是覺得代碼的執行是從前日後的,依次執行的。這麼說不能說徹底不對,在單線程程序裏,確實會這樣執行;可是在多線程併發時,程序的執行就有可能出現亂序。用一句話能夠總結爲:在本線程內觀察,操做都是有序的;若是在一個線程中觀察另一個線程,全部的操做都是無序的。前半句是指「線程內表現爲串行語義(WithIn Thread As-if-Serial Semantics)」,後半句是指「指令重排」現象和「工做內存和主內存同步延遲」現象。

  Java提供了兩個關鍵字volatile和synchronized來保證多線程之間操做的有序性,volatile關鍵字自己經過加入內存屏障來禁止指令的重排序,而synchronized關鍵字經過一個變量在同一時間只容許有一個線程對其進行加鎖的規則來實現,在單線程程序中,不會發生「指令重排」和「工做內存和主內存同步延遲」現象,只在多線程程序中出現。

 

happens-before原則

相關文章
相關標籤/搜索