I)、指令重排序
1、問題描述
首先一定要明確:指令重排序和有序性是不一樣的,這一點非常重要,
我們經常都會這么說:
①、volatile能保證記憶體可見性、禁止指令重排序但是不能保證原子性,
②、synchronized能保證原子性、可見性和有序性,
注意:這里的有序性并不是代表能禁止指令重排序,
舉個例子:
在雙重檢查的單例模式中,既然已經加了
synchronized為什么還需要volatile去修飾變數呢?如果synchronized能禁止指令重排,那么完全可以不用要volatile,
2、DCL代碼位元組碼分析指令重排序問題
首先需要知道的知識點:Object obj = new Object();這句代碼并不是一個原子操作,他分為三步:
- 在記憶體申請一片空間,new 出一個物件
- 呼叫new出的這個物件的構造方法
- 將這個物件的參考賦值給obj
a)、DCL雙重檢查代碼
public class MySingleton {
private static MySingleton INSTANCE;
private MySingleton() {
}
public static MySingleton getInstance() {
if (INSTANCE == null) {
synchronized (MySingleton.class) {
if (INSTANCE == null) {
INSTANCE = new MySingleton();
}
}
}
return INSTANCE;
}
}
b)、位元組碼如下
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-OYQAIyjU-1623231753387)(picture/禁止指令重排序問題-1.png)]](https://img.uj5u.com/2021/06/11/243453110803541.png)
從位元組碼中可以看到,new MySingleton();這句代碼對應了17、20、21、24這四行位元組碼(20行是一個參考的拷貝,可以忽略),
- 首先在17行在記憶體中開辟一塊空間創建一個MySingleton物件,
- 然后在21行呼叫該物件的構造方法,
- 然后在24行將該物件的參考賦值給靜態變數INSTANCE,
以上是我們期望的執行順序,我們希望每個執行緒都按照該順序去執行指令(這就是禁止指令重排序),但是由于計算機為了提高運行效率,會將我們的指令順序進行優化重排(比如上面的順序可能會優化重排為:17、24、21)
指令重排序帶來的問題
- 我們的計算機為了提升效率,會將我們的代碼順序做一些優化,比如在t1執行緒中的執行順序是 17、24、21,在t2執行緒中執行的順序是17、21、24 (在單個執行緒中不管是那種執行順序都不會有問題),
- 當t1執行緒獲取到鎖執行物件創建的時候,先執行了24行,將該物件的參考賦值給了靜態變數
INSTANCE(此時物件還沒呼叫構造方法,該物件還不是一個完整的物件), - 此時t2執行緒開始運行了,當t2執行緒執行到
if (INSTANCE == null)(第16行代碼)陳述句的時候,t2執行緒發現INSTANCE不為空,此時t2執行緒直接回傳INSTANCE物件,但是此時該物件還是一個不完整的物件,在t2執行緒使用該物件的時候就會出現問題,
所以說指令重排序在單執行緒中是不會有任何問題的,但是一旦涉及到多執行緒的情況,那么指令重排序可能會帶來意想不到的結果,
II)、有序性
那么既然synchronized不能禁止指令重排序,那么他保證的有序性是什么有序呢?
它的本質是讓多個執行緒在呼叫synchronized修飾的方法時,由并行(并發)變成串行呼叫,誰獲得鎖誰執行,
1、代碼示例
t1、t2兩個執行緒都需要去獲取單例物件,然后呼叫test方法,并且test方法是加了同步鎖的方法,
public class MySingleton {
private static MySingleton INSTANCE;
private MySingleton() {
}
public static MySingleton getInstance() {
if (INSTANCE == null) {
synchronized (MySingleton.class) {
if (INSTANCE == null) {
INSTANCE = new MySingleton();
}
}
}
return INSTANCE;
}
public static void test(final MySingleton singleton) {
synchronized (MySingleton.class) {
System.out.println(singleton);
}
}
}
測驗代碼
public class MySingletonTest {
// 可以看到兩個執行緒都需要去獲取單例物件,然后呼叫test方法,并且test方法是加了同步鎖的方法
public static void main(final String[] args) {
new Thread(() -> {
MySingleton instance = MySingleton.getInstance();
MySingleton.test(instance);
}, "t1").start();
new Thread(() -> {
MySingleton instance = MySingleton.getInstance();
MySingleton.test(instance);
}, "t2").start();
}
}
即使是t2執行緒獲得了未呼叫建構式的物件,那么在t2執行緒中再去呼叫MySingleton.test(instance);方法的時候,也并不會出現任何問題,因為使用了同步鎖,每個一加鎖執行的方法都變成了串行,將并發執行變成了串行,當t2執行緒獲取到鎖然后執行的時候,t1早已經釋放了鎖,此時instance也已經早就被實體化好了,所以不會出現問題,
所以synchronized保證順序性是指的將并發執行變成了串行,但并不能保證內部指令重排序問題,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/286587.html
標籤:其他
上一篇:極品webpack,打包方法,
下一篇:Python|冒泡演算法快速入門
