路漫漫其修遠兮,吾將上下而求索
在這本書中文版的第219頁有個例子,講lazy load時用到double check,double check比直接用同步的好處是,當Singleton初始化后,就不會有額外的同步操作。它的例子是
不幸的是,雙重檢查不會保證正常工作,因為編譯器會在Singleton的構(gòu)造方法被調(diào)用之前隨意給INSTANCE先付一個值。如果在INSTANCE引用被賦值之后而被初始化之前線程1被切換,線程2就會被返回一個對未初始化完全的單例類實例的引用。這樣在程序的其他方法中使用時可能會出現(xiàn)未知的錯誤。
個人一開始認為正確的寫法,應該是這樣的
利用一個tempInstance局部變量來排除返回實例未初始化完全的情況。因為每次判斷的都是局部變量,每個線程都會有一個自己的tempInstance,這樣就保證每個線程的tempInstance要么是初始化完全的要么就是未初始化的,不會出現(xiàn)中間的情況。要注意的是SingletonNew的(1)處是不能去掉的,比如線程構(gòu)造了一個實例,線程2此時等待在那里,線程2得到鎖,判斷tempInstance == null結(jié)果是true,又初始化了一次,這就不是單例了。(2)處的賦值順序也是不能顛倒的,如果顛倒就會出現(xiàn)和Singleton類一樣的情形。
Jvm編譯器會對生成的代碼進行優(yōu)化,重新排序,甚至移除它認為不必要的代碼,volatile變量之間也是沒有順序保證的。然而jvm保證了classloader load字節(jié)碼和靜態(tài)變量初始化的同步性,所有把singleton設置為靜態(tài)變量是沒有問題的。JMM保證了單線程執(zhí)行的效果和程序的順序是相同的。JVM對代碼的重新排序和優(yōu)化是對于程序不可見的,所以在例子2中我不應該假設執(zhí)行的順序。在讀volatile變量之前,寫行為確保執(zhí)行完畢,并且更新的值會從線程工作內(nèi)存(CPU緩存,寄存器)刷新到主內(nèi)存中,JMM禁止volatile讀入寄存器,其他線程讀取時也會重新load到工作內(nèi)存中,保證了一致性和可見性,避免讀取臟數(shù)據(jù)。以前一直以為volatile涉及的只是變量可見性問題,或者說對可見性的適用范圍沒有很好的理解,并不涉及JMM順序性和原子性問題。新的JMM對它進行了擴展,它對volatile變量的重新排序也做了限制。在舊的內(nèi)存模型當中,volatile變量的多次訪問之間是不能重新排序的,但是它們能在和對非volatile變量訪問代碼之間進行重新排序,新的內(nèi)存模型不同的是,volatile訪問行為在和非volatile變量的訪問行為的代碼之間重新排序加了一些限制。對volatile的寫行為就和synchronize方法或block釋放監(jiān)視器(鎖)的效果是一樣的,對volatile字段的讀操作和監(jiān)視器(鎖)的申請效果也是一樣的。新的模型在volatile字段訪問上做了一些嚴格的限制,只對當前線程可見的變量寫入到volatile共享變量f后,當其他線程讀取f后就是可見的。
下面這個簡單的例子:
class VolatileExample {
int x = 0;
volatile boolean v = false;
public void writer() {
x = 42;
v = true;
}
public void reader() {
if (v == true) {
//uses x - guaranteed to see 42.
假設當前一個線程正在調(diào)用writer方法,其他線程正在調(diào)用reader方法,writer方法中對v的寫行為將對x的寫行為釋放到了內(nèi)存中,v變量的讀取,又重新從內(nèi)存中獲取了新值。因此,如果讀方法看到了v的值被設為true,也保證了它在這之前就可以看到x的新值42,但這在舊的內(nèi)存模型中是不保證的。如果v不是volatile的,編譯器可能就會對writer和reader中的代碼進行重新排序,reader方法的訪問有可能得到的x就是0. 可見在新的JMM中,volatile的語義得到了很好的加強,每次對volatile字段的讀和寫可看作是都是半同步。這種順序性(happen-before關(guān)系)是針對同一個volatile字段而言的,對不同volatile字段的讀取還是沒有這種順序保證的。在新的JMM下,用volatile就可以解決問題,線程1實例的初始化和線程2的讀取volatile變量就存在一個happen-before關(guān)系。 JMM對順序性只是提出了一些規(guī)則,具體如何重新排序還是不得而知。 參考文章:http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#reordering 《JAVA Language Specification》 17.4
Powered by: BlogJava Copyright © 叱咤紅人