java內存模型
java虛擬機規範中試圖定義一個java內存模型來屏蔽掉各類硬件和操做系統的內存訪問差別,以實現讓java程序在各個平臺都能達到一致的內存訪問效果。java
1.主內存和工做內存
java內存模型的主要目標是定義程序中各個變量的訪問規則,即在虛擬機中將變量存儲到內存和從內存中取出變量這樣的底層細節。這些變量包括實例字段、靜態字段和構成數組對象的元素,包括局部變量和方法操做,由於是線程私有的,不會被共享。程序員
java內存模型規定了全部的變量都存儲在主內存上,此外每一個線程還有本身的工做內存,線程的工做內存中保存了被線程用到的變量的主內存副本拷貝,線程對變量的全部操做(讀取和賦值)都必須在工做內存中進行,不能直接讀寫主內存中的變量。線程間變量的傳值,須要經過主內存來完成。編程
2.內存間交互操做
主內存和工做內存的交互協議,即一個變量如何從主內存拷貝到工做內存、如何從工做內存同步會主內存的實現細節,java內存模型中定義了8中操做來完成,虛擬機必須保證每一種操做都是原子性的。數組
- lock(鎖定):做用於主內存的變量,把一個變量標識爲一個線程獨佔的狀態;
- unlock(解鎖):做用於主內存的變量,把一個處於鎖定狀態的變量釋放出來,能夠被其餘的線程訪問;
- read(讀取):做用於主內存變量,把一個變量的值從主內存傳輸到線程的工做內存中,以便隨後的load動做使用;
- load(載入):做用於工做內存的變量,它把read操做從主內存獲得的變量值放入工做內存的變量內存副本中;
- use:做用於工做內存的變量,把工做內存的一個變量的值傳遞給執行引擎,每當虛擬機遇到一個須要使用到變量的值的字節碼指令是都會執行這個操做;
- assign(賦值):做用於工做內存的變量,把從執行引擎的接受到的執行結果賦值給工做內存的變量;
- store(存儲):做用於工做內存的變量,把工做內存一個變量的值傳輸到主內存中,以便隨後write使用;
- write(寫入):做用於主內存的變量,把store操做從工做內存獲得的值放入主內存的變量中。
此外java內存模型還規定了在執行8中操做的時候必須知足如下規則:安全
- 不容許read和load、store和write單獨出現
- 不容許一個線程丟棄它的最近assign操做,即工做內存變量修改後必須同步回主內存;
- 不容許一個變量沒有任何assign操做就把數據從工做內存同步回主內存;
- 一個新的變量只能在主內存中」生成「,不容許工做線程使用一個未被初始化的變量,即對一個變量進行use、store操做以前,必需要有load、assign操做;
- 一個變量在同一時刻只容許一個線程對其進行lock操做;
- 對一個變量lock時,會清空工做內存此變量的值,在使用時從新load或assign來初始化變量值;
- 若是一個變量沒有lock操做,不容許對其執行unlock操做;
- 對一個變量執行unlock時必須執行store、write把變量值同步回主內存;
volatile類型變量
當一個變量定義爲volatile以後,它將具有兩種特性,第一是保證此變量對全部線程的可見性,當一個線程修改了這個變量的值以後,對於其餘線程而言是馬上得知的。當時java裏面的運算非原子操做,致使volatile變量的運算在併發環境下同樣時不安全的。併發
volatile變量只能保證可見性,在不符合下面兩條分這的運算場景中,依然要經過加鎖。app
- 運算結果並不依賴變量的值,或者可以保證只有單一線程修改變量的值;
- 變量不須要於其餘的狀態變量共同參與不變約束。
volatile第二個語義是禁止重排序。volatile的讀操做性能消耗與普通變量幾乎沒有差異,寫操做性能會差一點,須要插入內存屏障來禁止重排序。性能
java內存模型的特徵是:原子性、可見性、有序性。操作系統
- 原子性:由java內存模型來直接保證的原子操做包括,read、load、assign、use、store、write;synchronized塊之間也具有原子性。
- 可見性:是指當一個線程修改了共享變量的值,其餘線程可以馬上得知這個改變。除了volatile以外,synchronized和final也能實現可見性。
- 有序性:java自己的有序性,若是在本線程中觀察,全部操做都是有序的,若是在一個線程觀察另外一個線程,全部操做都是無序的。java語言提供了synchronized和volatile來保證線程之間的有序性。synchronized經過保證一個變量在同一時刻只有一個線程能對其進行lock操做保證的;volatile經過禁止重排序實現。 若是java內存模型中全部的有序性都僅僅靠volatile和synchronized來完成,那麼咱們編寫java代碼將會很是繁瑣。事實上咱們在編寫java併發代碼的時候並無感受到這一點,這是由於java語言中的happens-before原則。happens-before原則是JMM最核心的概念。
從JMM設計者的角度,在設計JMM是須要考慮到兩個關鍵因素:線程
- 程序員對內存模型的使用:程序員但願內存模型,易於理解,易於編程。程序員但願基於一個強內存模型來編寫代碼。
- 編譯器和處理器對內存模型的實現:編譯器和處理器但願對他們的約束越少越好,編譯器和處理器但願一個弱內存模型。
happens-before規則以下:
- 程序次序規則(Program Order Rule):在一個線程內,按照程序代碼順序;
- 管程鎖定原則(Monitor Lock Rule):一個unlock操做先行發生於後面對同一個鎖的lock操做;
- volatile變量規則(volatile variable Rule):對一個volatile變量的寫操做先發生於後面對這個變量讀;
- 線程啓動規則(Thread Start Rule):Thread對象的start()方法先行發生於此線程的每個動做;
- 線程終止規則(Thread Termination Rule):線程中的全部操做都先發生於對線程的終止檢測,Thread.isAlive
- 線程中斷規則(Thread Interruption Rule):對線程的interrupt方法的調用優先發生於被中斷線程的代碼檢測到中斷髮生。
- 對象終結操做(Finalizer Rule):一個對象的初始化完成,先行發生於它的finalizer
- 傳遞性(Transitivity):A先行發生於B,B先行發生於C,A先行發生於C
線程的實現
實現線程主要有3中方式:使用內核線程實現、使用用戶線程實現和使用用戶線程加輕量級進程混合實現。
- 使用內核線程實現 內核線程就是直接由操做系統內核(Kernel)支持的線程,這個線程由內核完成線程切換,內核經過操縱調度器(Schedule)對線程進行調度,並負責將線程的任務映射到各個處理器上。咱們的程序通常不會直接使用內核線程,而是去使用內核線程的一種高級接口——輕量級進程,輕量級進程就是咱們一般所說的線程。因爲是基於內核線程實現的,因此各類線程操做(建立、析構、同步)都須要進行系統調用。而系統調用的代價較高,須要在用戶態和內核態中來回切換。此外每一個輕量級進程都須要一個內核線程的支持,所以一個系統支持輕量級進程的數量是有限的。
- 使用用戶線程實現 從廣義上說,一個線程不是內核線程就能夠認爲是用戶線程。而狹義上的用戶線程指的是徹底創建在用戶空間的線程庫上,系統內核不能感知線程存在的實現。
線程調度
線程調度是指系統爲線程分配處理器使用權的過程,主要調度方式有兩種,分別是協同式線程調度和搶佔式線程調度。
線程狀態切換
java定義了5種線程狀態,在任意一個時間點,一個線程只能有且只有其中一中狀態。
- new:建立後未執行;
- runable:包括操做系統狀態的running和ready;
- waiting:無限期等待,處於這種狀態的線程不會被分配cpu執行時間,Object.wait()、Thread.join()沒有設置時間、LockSupport.park()方法;
- Timed waiting:處於這種狀態的線程不會被分配cpu執行時間,無需被其餘線程喚醒,必定時間後會由系統喚醒。Object.wait()、Thread.join()設置時間、Thread.sleep()等。
- blocked:阻塞狀態,和waiting不一樣的是,blocked狀態在等待得到一個排它鎖,在一個線程放棄這個鎖的時候發生。
- Terminal:線程執行結束。