【學習筆記】—JVM(五)內存模型與線程

一、Java內存模型

在這裏插入圖片描述
  創建這樣一個模型主要是爲了定義程序中各個變量的訪問規則。這也是Java跨平臺性的一個重要組成部分。因爲例如C/C++等語言都是直接使用物理硬件和操作系統的內存模型,會出現在這個平臺能夠正常訪問數據,換一個之後就報錯訪問不到或者訪問到錯誤數據。
  圖中的內存這些和前面的堆,棧,方法區,劃分層次不同基本沒有什麼關係

特徵

  • 原子性
    在一個操作中,CPU不可以在中途停止然後再調度,即不被中斷操作,要麼執行完成,要麼不執行
  • 可見性
    當多線程訪問同一個變量,一個線程修改了該變量的值,其他線程可立即看到修改的值
  • 有序性
    當多線程訪問同一個變量,一個線程修改了該變量的值,其他線程可立即看到修改的值。

Java天然有序性:在本程序重觀察,所有的操作都是有序的;在一個線程中觀察另一個線程,所有的操作都是無序的。前半句指“線程內表現爲串行語義”後半句指“指令重排序”現象和“工作內存與主內存同步延遲”現象。

保證原子性的8鍾操作

操作 作用範圍 描述
lock(鎖定) 主內存 把一個變量標記爲一條線程獨佔狀態
unlock(解鎖) 主內存 將一個處於鎖定狀態的變量釋放出來,釋放後的變量才能夠被其他線程鎖定
read(讀取) 工作內存 把變量值從主內存傳送到線程的工作內存中,以便隨後的load動作使用
load(載入) 工作內存 它把read操作的值放入工作內存中的變量副本中
use(使用) 工作內存 把工作內存中的值傳遞給執行引擎,每當虛擬機遇到一個需要使用這個變量的指令時候,將會執行這個動作
assig(賦值) 工作內存 把從執行引擎獲取的值賦值給工作內存中的變量,每當虛擬機遇到一個給變量賦值的指令時候,執行該操作
store(存儲) 工作內存 把工作內存中的一個變量傳送給主內存中,以備隨後的write操作使用
write(寫) 主內存 把store傳送值放到主內存中的變量中
  • read和load,store和write要按順序執行,且不能單一出現
  • use、store操作之前,必須先執行load、assign,即新變量只能在主內存中新生
  • lock一個變量,將會清空工作內存中此變量的值,在需要他的時候重新執行load或者assig對其初始化值

二、volatile變量

  最輕量的同步機制
  兩種特性:
    - 被修飾的變量對所有線程有可見性。
      在對volatile做修改時相當於對變量做了內存交互操作的store和write操作,由此對其他CPU立即可見
    - 禁止指令重排序
      通過內存屏障來實現,即重排序時不能把後面的指令重排序到內存屏障之前的位置。

指令重排序:爲了儘可能減少內存操作速度遠慢於CPU運行速度所帶來的CPU空置的影響,虛擬機會按照自己的一些規則(這規則後面再敘述)將程序編寫順序打亂——即寫在後面的代碼在時間順序上可能會先執行,而寫在前面的代碼會後執行——以儘可能充分地利用CPU

  volatile修飾的變量只能保證可見性而不能保證原子性,任然需要通過加鎖才能保證原子性,因此volatile變量只有在滿足以下條件才使用

  • 運算結果並不依賴變量的當前值,或者能夠確保只有單一的線程修改變量的值。
  • 變量不需要與其他的狀態變量共同參與不變約束。

  volatile變量讀寫性能消耗與普通變量相差無多,而且大多數情況下volatile變量的總開銷要比鎖低。

三、其他關鍵字的可見性

  synchronized和final也能實現可見性,同步塊的可見性是對一個變量執行unlock之前,必須把此變量同步回主內存中(store、write)。而被final修飾的字段在構造器中一旦初始化完成,並且構造器沒有把this的引用傳遞出去,那其他線程中就能看見final字段的值

四、先行發生原則

  總不可能所有的有序性都用關鍵子來規定。先行發生是判斷數據是都存在競爭,線程是否安全的主要依據。比如操作A產生的影響(改變值,發送消息,調用等)能被操作B觀察到,則A必然是要先於B發生的。

//A線程發生
i=1;

//B線程發生
j=i;

//C線程發生
i=2;

沒有C時,J必然是1,有了C,C和B沒有先行發生,那麼執行的順序時A->B->C還是A->C->B就不能確定了。

天然先行發生關係

  • 程序次序規則
  • 管程鎖定規則
  • volatile變量規則
  • 線程啓動規則
  • 線程終止規則
  • 線程中斷規則
  • 對象終結規則
  • 傳遞性

“時間上的先發生”不一定代表“先行發生”

五、Java與線程

線程的實現

  • 內核線程實現(1對1)
    內核線程(KTL),操作系統內核支持的線程。程序一般不會直接使用內核線程,而是去使用內核現成的高級接口——輕量級進程(LWP),也就是我們通常意義上的線程
    在這裏插入圖片描述
    這樣系統調用的代價相對較高,且每個輕量級進程都要內核線程支持,要消耗一定的內核資源,因此一個系統支持輕量級進程的數量是有限的。
  • 用戶線程實現(1對多)
    只要不是內核線程的都是用戶線程(UT)
    在這裏插入圖片描述
    因爲沒有內核線程的支援,所有的操作都要用戶程序自己處理
  • 用戶線程加輕量級進程混合實現(多對多)
    混合使用
    在這裏插入圖片描述

Java線程的調度方式爲搶佔式線程調度

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章