instance = new Single()這句,這并非是一個原子操作,在 JVM 中做了下面 3 件事情。 1. 給 instance 分配堆內(nèi)存(Single 對象) 2. 調(diào)用 Single的構(gòu)造函數(shù)來初始化成員變量,形成實例 3. 將instance 指針指向分配的內(nèi)存空間(執(zhí)行完這步 singleton才是非 null了)。 簡單說即,開辟堆空間,構(gòu)造,指向堆空間。 正常執(zhí)行順序:1->2->3,由于2和3沒有依賴性(1和3有依賴性),可能發(fā)生指令重排,可能的執(zhí)行順序為:1->3->2。 當1,3執(zhí)行后,instnce指針是不為null了,此時,另一個線程執(zhí)行 if(instance == null) 就會判斷是非空直接返回,而此時,Single的構(gòu)造還可能未執(zhí)行,會引發(fā)嚴重數(shù)據(jù)錯誤?。。?! 解決方法(volatile): volatile關(guān)鍵字的一個作用是禁止指令重排,把instance聲明為volatile之后,對它的寫操作就會有一個內(nèi)存屏障,這樣,在它的賦值完成之前,就不用會調(diào)用讀操作。 內(nèi)存屏障由來對于CPU的寫,目前主流策略有兩種: 1、write back:即CPU向內(nèi)存寫數(shù)據(jù)時,先把真實數(shù)據(jù)放入store buffer中,待到某個合適的時間點,CPU才會將store buffer中的數(shù)據(jù)刷到內(nèi)存中,而且這兩個操作是異步的。這在多線程環(huán)境中,有些情況下是可以接受的,但是有些情況是不可接受的,為了讓程序員有能力根據(jù)業(yè)務(wù)需要達到同步完成,就設(shè)計了內(nèi)存屏障。關(guān)于內(nèi)存屏障,后面會細講。 2、write through:即CPU向內(nèi)存寫數(shù)據(jù)時,同步完成寫store buffer與內(nèi)存。 當前CPU大多數(shù)采用的是write back策略??赡苡型獑柫耍簽槭裁茨??因為大多數(shù)情況下,CPU異步完成寫內(nèi)存產(chǎn)生的部分延遲是可以接受的,而且這個延遲極短。只有在多線程環(huán)境下需要嚴格保證內(nèi)存可見等極少數(shù)特殊情況下才需要保證CPU的寫在外界看來是同步完成的,需要借助CPU提供的內(nèi)存屏障實現(xiàn)。如果直接采用策略2:write through,那每次寫內(nèi)存都需要等待數(shù)據(jù)刷入內(nèi)存,極大影響了CPU的執(zhí)行效率。 內(nèi)存屏障實現(xiàn)為什么要插入屏障?本質(zhì)是業(yè)務(wù)層面不能接受寫store buffer與刷回內(nèi)存這兩個異步操作產(chǎn)生的哪怕是極少的延遲,即對內(nèi)存可見性的要求極高。 內(nèi)存屏障到底是什么?內(nèi)存屏障什么都不是,它只是一個抽象概念,就像OOP。如果這樣說你不理解,那你把他理解成一堵墻,這堵墻正面與反面的指令無法被CPU亂序執(zhí)行及這堵墻正面與反面的讀寫操作需有序執(zhí)行。 CPU提供了三個匯編指令串行化運行讀寫指令達到實現(xiàn)保證讀寫有序性的目的: SFENCE:在該指令前的寫操作必須在該指令后的寫操作前完成 LFENCE:在該指令前的讀操作必須在該指令后的讀操作前完成 MFENCE:在該指令前的讀寫操作必須在該指令后的讀寫操作前完成 何謂串行化?你可以理解成CPU把讀、寫、讀寫請求放入了一個隊列,按照先進先出的順序執(zhí)行下去;何謂讀操作完成,即CPU執(zhí)行一次讀操作,把值讀到寄存器中;何謂寫操作完成,即CPU執(zhí)行一次寫操作,數(shù)據(jù)刷到內(nèi)存中。 Java的volatile在實現(xiàn)層面用的不是fence族屏障,而是lock。lock是如何實現(xiàn)屏障效果的呢?JVM為什么不用fence族呢? 。。。 |
|