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