深入理解 Java 虛擬機:Java 內存模型與線程
什麼是內存模型?
內存模型: 可以理解爲,在特定的操作協議下,對特點的內存
或高速緩存
進行讀寫訪問
的過程的抽象
。
什麼是高速緩存?
在運算時,將需要使用到的數據從內存複製到緩存(Cache)中
,以此讓計算能更快的進行
,計算結束後再從緩存同步回內存中
,這樣就無需頻繁的等待緩慢的內存讀寫
。
緩存一致性
緩存一致性: 在多處理器系統中,每個處理器都有自己的高速緩存,而它們又共享同一主內存。
Java 內存模型
主內存
主內存是虛擬機內存的一部分。
Java 內存模型規定所的變量都存儲在主內存中。
變量數據: 最根源的數據,線程不能直接操作。
工作內存
工作內存中保存了被該線程使用到的變量
的主內存副本拷貝
,線程對變量的所有操作(讀取,賦值等)
,都必須在工作內存中進行
,不能直接讀寫主內存中的數據。不同線程間也無法直接訪問對方工作線程中的變量
。
變量數據: 從主內存中拷貝來的副本,其他線程無法訪問,操作結束後需寫回主內存。
內存間的交互操作
虛擬機實現時,必須保證下面提及的每一種操作都是原子的
、不可再分的
。
- lock(鎖定): 作用於
主內存
的變量,把變量標記爲某個線程獨佔。 - unlock(解鎖): 作用於
主內存
的變量,釋放一個鎖定的變量。 - read(讀取): 作用於
主內存
的變量,把一個變量的值從主內存傳輸到工作內存
中,以便後續load
動作使用。 - load(載入): 作用於
工作內存
的變量,將read
過來的值放入工作內存
的變量副本
中。 - use(使用): 作用於
工作內存
的變量,把工作內存
中存在的一個變量傳遞給執行引擎,每當虛擬機遇到一個需要使用變量的值時就會執行這個操作。 - assign(賦值): 作用於
工作內存
的變量,把從執行引擎接收到的值,賦值給工作內存
中的一個變量,當虛擬機遇到賦值操作時候執行操作。 - store(存儲): 作用於
工作內存
的變量,把工作內存
中的一個變量傳遞給主內存,一遍後續write
動作使用。 - write(寫入): 作用於
主內存
的變量,把store
操作的值放入主內存的變量中。
將變量從主內存複製到工作內存: 順序執行 read
和 load
,只需按順序即可,中間可插入其他指令。
從工作內存寫回主內存: 順序執行 sore
和 write
,只需按順序即可,中間可插入其他指令。
volatile 關鍵字
對所有線程可見性
特性: 保證此變量對所有線程的可見性
,當一條線程修改了這個變量值,其他線程會立即收到通知。
volatile 關鍵字保證線程可見,不代表線程安全:
以下爲 race ++
的反編譯結果
volatile 關鍵字
保證了線程可見性,因此只要去讀數據,就可以拿到正確的數據
而這裏的讀操作,只有 getstatic
字節碼指令,將數據讀入棧頂(每個線程各自擁有,對外部可見)
而後續的不管是遞增 iadd
還是回寫回主內存 putstatic
指令之前都沒有再去讀數據
因此如果這期間如果數據發生了變更
,在棧頂的數據就成了舊數據
,回寫回主內存就會產生覆蓋
,線程不安全
。
禁止指令重排序
普通變量僅僅會保證在該方法的執行過程中,所有依賴賦值結果的地方都能獲取到正確的結果
,而不能保證變量賦值操作的順序與程序代碼中的執行順序一致
。
這麼做的目的是一種機器級的優化操作,使得某個部分的彙編代碼被提前執行
。
volatile
可以避免這種的優化操作。
內存屏障:
volatile
是通過 內存屏障
來防止指令重排序的,反編譯後即下圖紅色框框中的部分。
作用: 防止 內存屏障
後面的指令排序到 內存屏障
之前。
Java 內存模型的特徵
Java 內存模型是圍繞 併發過程中
如何處理 原子性
、可見性
和 有序性
3 個特徵來建立的。
原子性
直接 保證原子性的操作
包括:read
、load
、assign
、use
、store
、write
基本數據類型的訪問讀寫都是具備原子性的
例外的只有 long
和 double
,具有非原子協定,但是各大虛擬機實現時,都把他們的讀寫操作作爲原子操作來對待,因此知道下就好了,不需要太在意。
爲了保證原子性,虛擬機提供字節碼 monitorenter
和 monitorexit
來操作 lock
和 unlock
,反應到代碼層面就是 synchronized
關鍵字,因此 synchronized
塊的操作也具有原子性。
可見性
可見性: 可見性是指,當一個線程修改了共享變量的值,其他變量能立即得知這個修改。
保證可見性的關鍵字: volatile
、synchronized
、final
。
有序性
有序性: 如果在本線程內觀察,所有操作都是有序的;如果在一個線程中觀察另一個線程,所有操作都是無需的。前半句是指 “線程內表現爲串行的語義”,後半句是指 “指令重排序” 現象和 “工作內存和主內存同步延遲” 現象。
防止指令重排序關鍵字: volatile
、synchronized
。
synchronized 缺點
synchronized
同時滿足 3 大特性,看起來十分 “萬能”,但過度濫用回導致性能問題。
先行發生原則
即多線程同時操作一個變量導致值不正確的線程不安全問題。
“天然的” 先行發生關係
即不需要同步器,在代碼中能直接使用
- 程序次序規則: 在一個線程內,按照程序代碼的順序,寫在前面的代碼比寫在後面的代碼先執行。
- 管程鎖定規則: 同一個鎖,一個 unlock 操作先行發生於後面對同一個鎖的 lock 操作。不解鎖就沒法再鎖定。
- volatile 變量規則: 對一個
volatile
變量的寫操作優先於後面這個變量的讀操作,“後面” 是指時間上的先後順序。 - 線程啓動規則: Thread 對象的 start() 方法先行發生於此線程的每個動作。不啓動線程,其他動作無法進行。
- 線程終止規則: 線程中所有操作都優先於此線程的終止檢查,可通過
Thread.join() 方法結束
、Thread.isAlive() 返回值
等手段檢查到線程已經終止執行。 - 線程中斷規則: 對線程 interrupt() 方法的調用先行發生於被中斷線程的代碼檢查到終端事件的發生,可通過
Thread.interrupt()
方法檢查到是否有中斷髮生。 - 對象終結規則: 一個對象的初始化完成(構造函數執行結束),先行發生於它的 finalize() 方法的發送
- 傳遞性: 操作 A 優先於操作 B,操作 B 優先於操作 C,操作 A 就必然優先於 操作 C