深入理解 Java 虛擬機:Java 內存模型與線程

什麼是內存模型?

內存模型: 可以理解爲,在特定的操作協議下,對特點的內存高速緩存進行讀寫訪問過程的抽象

什麼是高速緩存?

在運算時,將需要使用到的數據從內存複製到緩存(Cache)中,以此讓計算能更快的進行,計算結束後再從緩存同步回內存中,這樣就無需頻繁的等待緩慢的內存讀寫

緩存一致性

緩存一致性: 在多處理器系統中,每個處理器都有自己的高速緩存,而它們又共享同一主內存。
在這裏插入圖片描述

Java 內存模型

主內存

主內存是虛擬機內存的一部分。

Java 內存模型規定所的變量都存儲在主內存中。

變量數據: 最根源的數據,線程不能直接操作。

工作內存

工作內存中保存了被該線程使用到的變量主內存副本拷貝,線程對變量的所有操作(讀取,賦值等),都必須在工作內存中進行,不能直接讀寫主內存中的數據。不同線程間也無法直接訪問對方工作線程中的變量

變量數據: 從主內存中拷貝來的副本,其他線程無法訪問,操作結束後需寫回主內存。
在這裏插入圖片描述

內存間的交互操作

虛擬機實現時,必須保證下面提及的每一種操作都是原子的不可再分的

  • lock(鎖定): 作用於主內存的變量,把變量標記爲某個線程獨佔。
  • unlock(解鎖): 作用於主內存的變量,釋放一個鎖定的變量。
  • read(讀取): 作用於主內存的變量,把一個變量的值從主內存傳輸到工作內存中,以便後續 load 動作使用。
  • load(載入): 作用於工作內存的變量,將 read 過來的值放入工作內存變量副本中。
  • use(使用): 作用於工作內存的變量,把 工作內存 中存在的一個變量傳遞給執行引擎,每當虛擬機遇到一個需要使用變量的值時就會執行這個操作。
  • assign(賦值): 作用於工作內存的變量,把從執行引擎接收到的值,賦值給工作內存中的一個變量,當虛擬機遇到賦值操作時候執行操作。
  • store(存儲): 作用於工作內存的變量,把工作內存中的一個變量傳遞給主內存,一遍後續 write 動作使用。
  • write(寫入): 作用於主內存的變量,把 store 操作的值放入主內存的變量中。

將變量從主內存複製到工作內存: 順序執行 readload,只需按順序即可,中間可插入其他指令。

從工作內存寫回主內存: 順序執行 sorewrite,只需按順序即可,中間可插入其他指令。

volatile 關鍵字

對所有線程可見性

特性: 保證此變量對所有線程的可見性,當一條線程修改了這個變量值,其他線程會立即收到通知。

volatile 關鍵字保證線程可見,不代表線程安全:
以下爲 race ++ 的反編譯結果
在這裏插入圖片描述
volatile 關鍵字 保證了線程可見性,因此只要去讀數據,就可以拿到正確的數據

而這裏的讀操作,只有 getstatic 字節碼指令,將數據讀入棧頂(每個線程各自擁有,對外部可見)

而後續的不管是遞增 iadd 還是回寫回主內存 putstatic 指令之前都沒有再去讀數據

因此如果這期間如果數據發生了變更,在棧頂的數據就成了舊數據回寫回主內存就會產生覆蓋線程不安全
在這裏插入圖片描述

禁止指令重排序

普通變量僅僅會保證在該方法的執行過程中,所有依賴賦值結果的地方都能獲取到正確的結果,而不能保證變量賦值操作的順序與程序代碼中的執行順序一致

這麼做的目的是一種機器級的優化操作,使得某個部分的彙編代碼被提前執行

volatile 可以避免這種的優化操作。

內存屏障:
volatile 是通過 內存屏障 來防止指令重排序的,反編譯後即下圖紅色框框中的部分。

作用: 防止 內存屏障 後面的指令排序到 內存屏障 之前。
在這裏插入圖片描述

Java 內存模型的特徵

Java 內存模型是圍繞 併發過程中 如何處理 原子性可見性有序性 3 個特徵來建立的。

原子性

直接 保證原子性的操作 包括:readloadassignusestorewrite

基本數據類型的訪問讀寫都是具備原子性的
例外的只有 longdouble,具有非原子協定,但是各大虛擬機實現時,都把他們的讀寫操作作爲原子操作來對待,因此知道下就好了,不需要太在意。

爲了保證原子性,虛擬機提供字節碼 monitorentermonitorexit 來操作 lockunlock,反應到代碼層面就是 synchronized 關鍵字,因此 synchronized 塊的操作也具有原子性。

可見性

可見性: 可見性是指,當一個線程修改了共享變量的值,其他變量能立即得知這個修改。

保證可見性的關鍵字: volatilesynchronizedfinal

有序性

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

防止指令重排序關鍵字: volatilesynchronized

synchronized 缺點

synchronized 同時滿足 3 大特性,看起來十分 “萬能”,但過度濫用回導致性能問題。

先行發生原則

即多線程同時操作一個變量導致值不正確的線程不安全問題。

“天然的” 先行發生關係

即不需要同步器,在代碼中能直接使用

  • 程序次序規則:一個線程內,按照程序代碼的順序,寫在前面的代碼比寫在後面的代碼先執行
  • 管程鎖定規則: 同一個鎖,一個 unlock 操作先行發生於後面對同一個鎖的 lock 操作。不解鎖就沒法再鎖定。
  • volatile 變量規則: 對一個 volatile 變量的寫操作優先於後面這個變量的讀操作,“後面” 是指時間上的先後順序。
  • 線程啓動規則: Thread 對象的 start() 方法先行發生於此線程的每個動作。不啓動線程,其他動作無法進行。
  • 線程終止規則: 線程中所有操作都優先於此線程的終止檢查,可通過 Thread.join() 方法結束Thread.isAlive() 返回值 等手段檢查到線程已經終止執行。
  • 線程中斷規則: 對線程 interrupt() 方法的調用先行發生於被中斷線程的代碼檢查到終端事件的發生,可通過 Thread.interrupt() 方法檢查到是否有中斷髮生。
  • 對象終結規則: 一個對象的初始化完成(構造函數執行結束),先行發生於它的 finalize() 方法的發送
  • 傳遞性: 操作 A 優先於操作 B,操作 B 優先於操作 C,操作 A 就必然優先於 操作 C
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章