日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

双重检查(Double-Check)

發布時間:2024/4/13 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 双重检查(Double-Check) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

有一個優化的思路,就是把100%出現的防護盾,也改為1%的幾率出現,使之只出現在可能會導致多個實例出現的地方。

代碼如下:

// 代碼三 public class Singleton {private static Singleton instance = null;private Singleton() {}public static Singleton getInstance() {if (instance == null){synchronized(Singleton.class){if (instance == null)instance = new Singleton();}}return instance;} }

這段代碼看起來有點復雜,注意其中有兩次if(instance==null)的判斷,這個叫做『雙重檢查 Double-Check』。

  • 第一個 if(instance==null),其實是為了解決代碼二中的效率問題,只有instance為null的時候,才進入synchronized的代碼段大大減少了幾率。
  • 第二個if(instance==null),則是跟代碼二一樣,是為了防止可能出現多個實例的情況。

這段代碼看起來已經完美無瑕了。當然,只是『看起來』,還是有小概率出現問題的。想要充分理解需要先弄清楚以下幾個概念:原子操作、指令重排。

原子操作

簡單來說,原子操作(atomic)就是不可分割的操作,在計算機中,就是指不會因為線程調度被打斷的操作。比如,簡單的賦值是一個原子操作:

m = 6; // 這是個原子操作

假如m原先的值為0,那么對于這個操作,要么執行成功m變成了6,要么是沒執行 m還是0,而不會出現諸如m=3這種中間態——即使是在并發的線程中。

但是,聲明并賦值就不是一個原子操作:

int n=6;//這不是一個原子操作

對于這個語句,至少有兩個操作:①聲明一個變量n ②給n賦值為6——這樣就會有一個中間狀態:變量n已經被聲明了但是還沒有被賦值的狀態。這樣,在多線程中,由于線程執行順序的不確定性,如果兩個線程都使用m,就可能會導致不穩定的結果出現。

指令重排

簡單來說,就是計算機為了提高執行效率,會做的一些優化,在不影響最終結果的情況下,可能會對一些語句的執行順序進行調整。比如,這一段代碼:

int a ; // 語句1 a = 8 ; // 語句2 int b = 9 ; // 語句3 int c = a + b ; // 語句4

正常來說,對于順序結構,執行的順序是自上到下,也即1234。但是,由于指令重排的原因,因為不影響最終的結果,所以,實際執行的順序可能會變成3124或者1324。由于語句3和4沒有原子性的問題,語句3和語句4也可能會拆分成原子操作,再重排。——也就是說,對于非原子性的操作,在不影響最終結果的情況下,其拆分成的原子操作可能會被重新排列執行順序。

主要在于singleton = new Singleton()這句,這并非是一個原子操作,事實上在 JVM 中這句話大概做了下面 3 件事情。

1. 給 singleton 分配內存

2. 調用 Singleton 的構造函數來初始化成員變量,形成實例

3. 將singleton對象指向分配的內存空間(執行完這步 singleton才是非 null了)在JVM的即時編譯器中存在指令重排序的優化。

也就是說上面的第二步和第三步的順序是不能保證的,最終的執行順序可能是 1-2-3 也可能是 1-3-2。如果是后者,則在 3 執行完畢、2 未執行之前,被線程二搶占了,這時 instance 已經是非 null 了(但卻沒有初始化),所以線程二會直接返回 instance,然后使用,然后順理成章地報錯。

再稍微解釋一下,就是說,由于有一個『instance已經不為null但是仍沒有完成初始化』的中間狀態,而這個時候,如果有其他線程剛好運行到第一層if (instance ==null)這里,這里讀取到的instance已經不為null了,所以就直接把這個中間狀態的instance拿去用了,就會產生問題。這里的關鍵在于線程T1對instance的寫操作沒有完成,線程T2就執行了讀操作。

對于代碼三出現的問題,解決方案為:給instance的聲明加上volatile關鍵字

代碼如下:

public class Singleton {private static volatile Singleton instance = null;private Singleton() {}public static Singleton getInstance() {if (instance == null){synchronized(Singleton.class){if (instance == null)instance = new Singleton();}}return instance;} }

volatile關鍵字的一個作用是禁止指令重排,把instance聲明為volatile之后,對它的寫操作就會有一個內存屏障,這樣,在它的賦值完成之前,就不用會調用讀操作。

注意:volatile阻止的不是singleton = new Singleton()這句話內部[1-2-3]的指令重排,而是保證了在一個寫操作([1-2-3])完成之前,不會調用讀操作(if (instance == null))。

?

總結

以上是生活随笔為你收集整理的双重检查(Double-Check)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。