Java內存模型和線程

Java內存模型和線程

什麼是內存模型

現代計算機的運算速度同存儲,IO之間的速度存在巨大差別,爲了彌補這個差別,讓CPU儘量的執行更屢次的運算,壓榨出更多的運算性能,因而有了讓計算機同時處理多項任務的手段。可是爲了匹配上CPU的運算速度與IO之間的速度差別,現代計算機引入了緩存的概念,即加入一層與CPU的運算速度相近的高速緩存,將運算所須要的數據從低速緩存或者內存甚至磁盤中複製到高速緩存中,使得CPU能夠儘快的開始計算,當運算結束以後,再將結果從緩存同步回內存中。數組

雖然引入緩存,解決了CPU與IO之間的速度差別,可是也帶來了緩存一致性問題,CPU的每一個處理器都有本身的高速緩存,在對內存中的同一塊數據區域存在併發操做的時候,咱們須要讓各個緩存器聽從一些協議,保證各個緩存器拿到的數據,都是相同且正確的。緩存

緩存一致性原則:再多處理器系統中,每個處理器都有本身的高速緩存,這些高速緩存共享同一主內存,當多個處理器涉及到的共享主內存爲同一區域的時候,將可能致使各自的高速緩存區域中的數據不一致問題,因此須要一些操做協議來決定在這種狀況下,按照什麼規則將主內存的數據更新,而在特定操做協議下,對特定內存或者高速緩存進行讀寫的訪問過程抽象就是「內存模型」。
圖片描述安全

Java內存模型

Java內存模型又叫JMM(Java Memory Model),主要定義了程序中各個變量的訪問規則,更具體的說是在JVM中將變量(實例字段,靜態字段,和構成數組對象的元素,不包括局部變量和方法參數)存儲到內存和從內存中取出變量的底層細節。多線程

Java內存模型包括兩個部分:主內存(全部的變量都存儲在主內存中)和工做內存(每個線程都有一個工做內存,保存該線程使用到的變量的主內存副本),線程對變量的操做只能在工做內存中進行,線程間變量的傳遞也必須經過主內存完成,沒法直接再線程間進行直接變量傳遞。
圖片描述併發

volatile修飾符:

能夠認爲這是JVM提供的最輕量級的同步機制,一個volatile修飾的變量具備兩種特性:該變量對全部線程可見,即若是一個線程修改了該變量的值,其它線程能夠當即得知這個變化。對於普通變量而言,線程A在工做內存中修改了某一變量的值以後,會寫會主內存,線程B只有在這個A的寫回任務完成以後,去從主內存讀取這個變量,新修改的值纔會對線程B可見。性能

volatile實現原理:

JVM解決緩存不一致問題的方式是,當對用volatile修飾的變量進行寫操做的時候,JVM向CPU發送一條Lock指令,鎖定主內存的變量,標識這個變量爲某一個工做線程獨有,而後CPU將工做內存中的數據寫回到主內存,同時會將其它工做內存中的該數據置爲失效,這樣當其餘線程從它的工做內存中讀取該變量的時候,將會強制性的從主內存中進行讀取。spa

但這並不意味着,volatile修飾的變量是併發安全的,即咱們不能簡單的認爲,對volatile變量的操做在併發條件下必定最終能夠獲得正確結果,緣由是在於:Java裏的運算操做,好比一個自增運算,會對應到三條字節碼指令:取常量1,add運算,寫回。更嚴謹的緣由是,即使某個運算編譯成字節碼指令對應到的是原子指令,它也可能須要若干條機器碼指令實現。操作系統

那麼volatile的適用場景就是:線程

1、運算結果不依賴當前變量的值,或者能夠確保,任什麼時候刻,只有單一線程會對該變量進行修改操做。對象

2、變量不須要與其它狀態變量共同參與不變約束,就是說,volatile變量能夠單獨肯定一個狀態,好比咱們能夠用volatile變量來控制併發。

volatile變量的第二層含義就是禁止指令重排,指令重排指的就是再現代微處理器上,爲了提升機器的運行效率,會採起將指令亂序執行的方式,在條件容許的狀況直接執行當前有能力執行的後續指令,避開由於等待順序指令所須要的時間。對於普通變量而言,只要可以保證指令重排後最終的計算結果與指令順序執行的結果保持一致,重排操做就是正確的(例如a--和a++,咱們假設認爲這兩條是原子操做,他們誰先執行和誰後執行,最終獲得的a的值都是同樣的)。可是有些狀況是不容許指令重排的:

1.寫一個變量,讀一個變量:若是讀重排到寫以前,那麼就錯誤。

2.寫一個變量,再寫一個變量:a=1;a=2若是或者先執行,最終a=1,錯誤

3.讀一個變量,再寫一個變量:a=b;b=1,假設b先前爲2,這兩條指令重排的會致使最紅a=1,錯誤。

Java內存模型中大致上對於基本數據類型的訪問讀寫是具有原子性的(long和double例外),可是對於好比引用類型,JMM提供了lock和unlock操做(彙編級別)知足對非基本類型的原子操做,這兩個操做再更高層次(字節碼指令級別)提供了monitorenter和monitorexit指令來隱式實現(monitor又叫管程,兩個字節碼指令須要控制一個對象,這個對象叫鎖,在兩個指令之間的字節碼指令,不會出現兩個線程能夠同時執行其間的狀況),這兩條指令在代碼中的體現就是同步代碼塊了(synchronized修飾的代碼塊)。

Java與線程:

線程實現方式

線程的實現有三種方式:

1.使用內核線程:直接藉助操做系統的內核支持的線程,線程之間的調度由內核經過操縱調度器完成,每個內核線程能夠視爲內核的一個分身。也能夠用輕量級進程實現,輕量級進程就是咱們一般說的線程,一個輕量級進程都由一個內核線程支持,即OS必須先支持內核線程,而後纔能有輕量級進程。可是由於基於內核線程,因此這種方式實現的線程操做,都須要經過系統調用來完成,而系統調用的代價比較高,並且須要消耗必定的內存資源,因此一個OS所支持的輕量級進程的數量是有限的。

2.使用用戶線程:一個線程若是不是內核線程,就能夠認爲是用戶線程,在這個角度上看,輕量級進程也是用戶線程的一種,狹義上說,用戶線程就是徹底創建在用戶空間的線程庫上的線程,用戶線程的管理調度等徹底由在用戶態中完成,不須要通過內核,所以相對於使用內核線程實現,用戶線程消耗的資源更少,也能夠支持更大規模的線程數量。

3.用戶線程+輕量級進程混合實現:該方法綜合輕量級進程和用戶線程兩種方式的優勢:輕量級進程做爲用戶線程和內核線程之間的橋樑,系統調用經過輕量級進程完成。

Java線程的實現再JDK1.2以前是基於用戶線程實現,再JDK1.2及之後,被替換爲OS原生線程模型實現。而從這裏,也體現了Java的平臺無關特性實際上是由於不一樣平臺的JVM實現雖然不一樣,可是這些不一樣的實現向上提供的確實相同的API。

線程調度方式

線程調度方式分爲兩種:

1.協同式線程調度:線程的執行時間由線程自身控制,當一個線程完成本身的任務以後,主動通知系統切換到另外一個線程,優勢是實現簡單,缺點是若是一個線程出現問題,可能程序會一直阻塞。

2.搶佔式線程調度:線程的執行事件由系統分配,沒法自身決定本身的執行時間,切換也不禁自身決定。由於控制權交給了系統,因此不會出現一個線程阻塞致使整個進程阻塞的問題,Java的線程調度基於搶佔式線程調度實現。

線程的狀態和狀態轉換:

線程的五種狀態:

1.新建:new,建立了線程可是沒有啓動它。

2.運行:runnable:該狀態的線程多是正在運行running,也多是正在等待CPU爲其分配計算時間ready。

3.1.無限期等待:waiting:這種狀態下的線程,CPU不會爲其主動分配時間,須要等到被其它線程顯式喚醒,讓一個線程進入無限期等待的方式是:不設置時間參數的Object.wait(),不設置時間參數的Thread.join(),LockSupport.park(),

3.2.期限等待:timed waitting:情形同上,只是能夠設定時間參數,由系統在必定時間後喚醒它們,Thread.sleep(),Object.wait,Thread.join,LockSupport.parkNanos/parkUntil

4.阻塞:阻塞態與等待態的差別在於,阻塞態在等待一個獲取一個鎖(排它鎖),當另外一個線程放棄這個鎖,而後該線程獲取到這個鎖以後,CPU將爲其分配執行時間,而等待狀態則是,線程等待必定時間或者等待被其它線程喚醒。當程序進入同步區域(同步代碼塊)的時候,將進入阻塞狀態(若是它沒有獲取到這個鎖的話)。

5.結束:Terminated:線程執行完畢,是已經終止線程的狀態。

這五種狀態的轉換,以下圖:
圖片描述
上面涉及到的幾個方法的做用和差別:
start方法是開啓一個線程的方法,run方法是線程中的具體任務,若是直接調用run方法,等同於直接調用線程對象的通常方法。

對於sleep()方法,咱們首先要知道該方法是屬於Thread類中的。而wait()方法,則是屬於Object類中的。sleep()方法致使了程序暫停執行指定的時間,讓出cpu該其餘線程,可是他的監控狀態依然保持者,當指定的時間到了又會自動恢復運行狀態;在調用sleep()方法的過程當中,線程不會釋放對象鎖。而當調用wait()方法的時候,線程會放棄對象鎖,進入等待此對象的等待鎖定池,只有針對此對象調用notify()方法後本線程才進入對象鎖定池準備。

notify方法只喚醒一個等待(對象的)線程並使該線程開始執行。因此若是有多個線程等待一個對象,這個方法只會喚醒其中一個線程,選擇哪一個線程取決於操做系統對多線程管理的實現。notifyAll 會喚醒全部等待(對象的)線程,可是具體哪個線程將會第一個處理仍然取決於操做系統的實現(即使設置了優先級,也只是對虛擬機的一個建議,並不以定徹底按照這個建議來從優先級高的開始處理)。若是當前狀況下有多個線程須要被喚醒,推薦使用notifyAll 方法。

相關文章
相關標籤/搜索