是因為指令重排造成的。直接原因也就是 初始化一個物件並使一個引用指向他 這個過程不是原子的。導致了可能會出現引用指向了物件並未初始化好的那塊堆記憶體,使用volatile修飾物件引用,防止重排序即可解決。推薦使用內部靜態類做延時初始化,更合適,更可靠。這個同步過程由JVM實現了。================================拿出來解釋一下吧:help = new Help();主要原因就是這個操作不是原子性的,從而留給了JVM重排序的機會。JVM的重排序也是有原則的,在單執行緒中,不管怎麼排,保證最終結果一致。注意是單執行緒。多執行緒的情況下指令重排序就會給程式設計師帶來問題。help = new Help()這個操作可以拆成:1、棧記憶體開闢空間給help引用2、堆記憶體開闢空間準備初始化物件3、初始化物件4、棧中引用指向這個堆記憶體空間地址指令重排之後可能會是1、2、4、3;這樣重排之後對單個執行緒來說效果是一樣的,所以JVM認為是合法的重排序,但是在多執行緒環境下就會出問題,這裡到4的時候help已經指向了一塊堆記憶體!=null ,只是這塊堆記憶體還沒初始化就直接返回了,使用的時候拋NullPointException。 當然這裡的幾個步驟並不算真正的指令,指令的粒度只會比這個還小,但是可以說明問題。 加入volatile之後檢視彙編程式碼可以發現多了一句 lock addl $0x0,(%esp)相當於一個記憶體屏障。volatile的作用:保證記憶體可見性,防止指令重排序,並不保證操作原子性。這裡用到的就是防止指令重排序的性質。如何實現這些性質的================保證可見性:使用該變數必須重新去主記憶體讀取,修改了該變數必須立刻重新整理主記憶體。防止重排序:透過插入記憶體屏障。不保證操作原子性不保證操作原子性不保證操作原子性=========================手機純手打。。。
是因為指令重排造成的。直接原因也就是 初始化一個物件並使一個引用指向他 這個過程不是原子的。導致了可能會出現引用指向了物件並未初始化好的那塊堆記憶體,使用volatile修飾物件引用,防止重排序即可解決。推薦使用內部靜態類做延時初始化,更合適,更可靠。這個同步過程由JVM實現了。================================拿出來解釋一下吧:help = new Help();主要原因就是這個操作不是原子性的,從而留給了JVM重排序的機會。JVM的重排序也是有原則的,在單執行緒中,不管怎麼排,保證最終結果一致。注意是單執行緒。多執行緒的情況下指令重排序就會給程式設計師帶來問題。help = new Help()這個操作可以拆成:1、棧記憶體開闢空間給help引用2、堆記憶體開闢空間準備初始化物件3、初始化物件4、棧中引用指向這個堆記憶體空間地址指令重排之後可能會是1、2、4、3;這樣重排之後對單個執行緒來說效果是一樣的,所以JVM認為是合法的重排序,但是在多執行緒環境下就會出問題,這裡到4的時候help已經指向了一塊堆記憶體!=null ,只是這塊堆記憶體還沒初始化就直接返回了,使用的時候拋NullPointException。 當然這裡的幾個步驟並不算真正的指令,指令的粒度只會比這個還小,但是可以說明問題。 加入volatile之後檢視彙編程式碼可以發現多了一句 lock addl $0x0,(%esp)相當於一個記憶體屏障。volatile的作用:保證記憶體可見性,防止指令重排序,並不保證操作原子性。這裡用到的就是防止指令重排序的性質。如何實現這些性質的================保證可見性:使用該變數必須重新去主記憶體讀取,修改了該變數必須立刻重新整理主記憶體。防止重排序:透過插入記憶體屏障。不保證操作原子性不保證操作原子性不保證操作原子性=========================手機純手打。。。