緩存一致性問題
JVM緩存一致性問題解決協議?
Java內存模型中定義了以下8中操作來完成主內存與工作內存之間交互的實現細節:
1、luck(鎖定):作用於主內存的變量,它把一個變量標示爲一條線程獨佔的狀態。
2、unlock(解鎖):作用於主內存的變量,它把一個處於鎖定狀態的變量釋放出來,釋放後的變量纔可以被其他線程鎖定。
3、read(讀取):作用於主內存的變量,它把一個變量的值從主內存傳輸到工作內存中,以便隨後的load動作使用。
4、load(載入):作用於工作內存的變量,它把read操作從主內存中得到的變量值放入工作內存的變量副本中。
5、use(使用):作用於工作內存的變量,它把工作內存中的一個變量的值傳遞給執行引擎,每當虛擬機遇到一個需要使用到變量的值得字節碼指令時將會執行這個操作。
6、assign(賦值):作用於工作內存的變量,它把一個從執行引擎接收到的值賦給工作內存的變量,每當虛擬機遇到一個給變量賦值的字節碼指令時執行這個操作。
7、store(存儲):作用於工作內存的變量,它把工作內存中的一個變量的值傳遞到主內存中,以便隨後的write操作使用。
8、write(寫入):作用於主內存的變量,它把store操作從工作內存中得到的變量值放入主內存的變量中。
Java內存模型還規定了執行上述8種基本操作時必須滿足如下規則:
1、不允許read和load、store和write操作之一單獨出現,以上兩個操作必須按順序執行,但沒有保證必須連續執行,也就是說,read與load之間、store與write之間是可插入其他指令的。
2、不允許一個線程丟棄它的最近的assign操作,即變量在工作內存中改變了之後必須把該變化同步回主內存。
3、不允許一個線程無原因地(沒有發生過任何assign操作)把數據從線程的工作內存同步回主內存中。
4、一個新的變量只能從主內存中“誕生”,不允許在工作內存中直接使用一個未被初始化(load或assign)的變量,換句話說就是對一個變量實施use和store操作之前,必須先執行過了assign和load操作。
5、一個變量在同一個時刻只允許一條線程對其執行lock操作,但lock操作可以被同一個條線程重複執行多次,多次執行lock後,只有執行相同次數的unlock操作,變量纔會被解鎖。
6、如果對一個變量執行lock操作,將會清空工作內存中此變量的值,在執行引擎使用這個變量前,需要重新執行load或assign操作初始化變量的值。
7、如果一個變量實現沒有被lock操作鎖定,則不允許對它執行unlock操作,也不允許去unlock一個被其他線程鎖定的變量。
8、對一個變量執行unlock操作之前,必須先把此變量同步回主內存(執行store和write操作)。
先行發生原則
-
程序次序規則(Program Order Rule):在一個線程中,按照程序代碼順序,書寫在前面的代碼操作先行發生於後面的操作
-
管程鎖定規則(Monitor Lock Rule):一個 unlock 操作先行發生於後面對同一個鎖的 lock 操作。“後面”指的是時間的先後順序。
-
volatile 變量規則(Volatile Variable Rule):對一個 volatile 變量的寫操作先行發生於後面對這個變量的讀操作。“後面”指的是時間的先後順序。
-
線程啓動規則(Thread Start Rule):Thread 對象的 start() 方法先行發生於此線程的每一個動作。
-
線程終止規則(Thread Termination Rule):線程中所有操作先行發生於對此線程的終止檢測。
-
線程中斷規則(Thread Interruption Rule):對線程 interrupt() 方法的調用先行發生於代碼檢測到中斷事件的發生。
-
對象終結規則(Finalizer Rule):一個對象的初始化完成(構造函數結束)先行發生於它的 finalize() 方法的開始。
-
傳遞性(Transitivity):如果操作A先行發生於操作B,操作B先行發生於操作C,那麼操作A先行發生於操作C。
volatile變量的特點
1、保證可見性,也就是說對volatile變量進行寫入操作的時候,其它線程能立即看到變化,而普通變量是做不到的。實現的原理就是volatile變量每次使用前都會去刷新,而每次寫入都會實時同步到主內存中。
2、volatile變量並不是併發安全的,雖然我們能立即看到volatile變量的最新值,但volatile變量參與的運算,並不是一個原子操作。
volatile變量適用的場景就是一個控制線程操作,其它work線程依賴它來進行決策,這種情況下不需要用鎖就能實現併發安全的效果
原子類的實現基礎
硬件支持的組合原子指令(CAS)是實現基礎,如:
測試並設置;
獲取並增加;
交換;
比較並交換;
加載鏈條/條件存儲;
線程安全的分類
線程安全的實現方法
鎖優化的常用手段有哪些?
鎖作爲最常用保證線程安全的工具,用的不好會導致死鎖,或者本來可以併發的變成了串行,降低了執行效率。
死鎖問題靠的是編碼邏輯實現的謹慎,而鎖的優化,則能大大的降低鎖對程序效率的影響。
1、自旋鎖與自適應自旋;
不讓出對CPU的佔用時間,而是循環等待獲得對應的鎖。如果鎖佔用時間很短,自旋等待的效果就會非常好;反之,如果鎖被佔用的時間特別長,那麼自旋的線程只會白白的浪費處理器資源,而不會做任何有用的工作,反而會帶來性能上的浪費;我的理解是如果自旋的時間比線程上下文切換的代價還要大,就不如直接阻塞而不是自旋了。
2、鎖消除;
虛擬機檢測到一段代碼上不存在共享數據競爭的問題,就會把鎖消除;
3、鎖粗化;
一般我們是儘量把鎖的範圍減小。但是有時候反其道而行之也能提高效率,在一段代碼中反覆對同一個鎖加鎖解鎖,就可以直接對整塊代碼加鎖,以減少操作鎖的開銷;
4、輕量級鎖;
5、偏向鎖;