我先貼出正確的寫法,在分析(注意標紅部分):
(1) 避免鎖定整個 getInstance() ,如果鎖定整個獲取實例的方法,那麼多線程每次獲取的時候,都有可能等待,等其他線程執行完,會有性能的損失。所以在先在(1)處判斷一下,非空的話,直接拿出來用。
(2)第二個if(instance==null) 是因爲,進入同步塊的時候,可能其他線程已經創建完畢,所以再判斷一下。
(3)volatile 的作用是多線程之間,保證變量instance的可見性,避免因爲每個線程的工作內存裏面的變量instance 的值不同而取髒數據。這裏我簡單說一下工作內存:每個線程有自己的工作內存的,如果工作內存沒有它要用的對象,它會到主內存去讀,它更新這個對象的時候,它會先寫到工作內存,然後再寫到主內存。由於每個線程都有自己的工作內存,所以可能會讀髒數據。當對象加了volatile ,就會保證 該對象一旦改變,會寫到主內存,並且通知其他線程到主內存去讀。
instance = new Singleton(); 這句在執行的時候,可以分解爲3行僞代碼如下:
1 memory=allocate();// 分配內存 相當於c的malloc
2 ctorInstanc(memory) //初始化對象
3 instance=memory //設置instance指向剛分配的地址
上面的代碼在編譯器運行時,可能會出現重排序 從1-2-3 排序爲1-3-2
如此在多線程下就會出現問題
例如現在有2個線程A,B
線程A在執行new Singleton()時,B線程進來,而此時A執行了 1和3,沒有執行2,此時B線程判斷instance不爲null (分配了空間即非空,但是對象還沒初始化), 直接返回一個未初始化的對象,就會出現問題
而用了volatile,上面的重排序就會在多線程環境中禁止,不會出現上述問題。