1. 程式人生 > >雙重檢查鎖機制

雙重檢查鎖機制

背景:我們在實現單例模式的時候往往會忽略掉多執行緒的情況,就是寫的程式碼在單執行緒的情況下是沒問題的,但是一碰到多個執行緒的時候,由於程式碼沒寫好,就會引發很多問題,而且這些問題都是很隱蔽和很難排查的。

例子1:沒有volatile修飾的uniqueInstance

複製程式碼
public class Singleton {
    private static Singleton uniqueInstance;

    private Singleton(){
    }

    public static Singleton getInstance(){
        if(uniqueInstance == null
){ //#1 synchronized(Singleton.class){ //#2 if(uniqueInstance == null){ //#3 uniqueInstance = new Singleton(); //#4 System.out.println(Thread.currentThread().getName() + ": uniqueInstance is initalized..."); //#5.1 } else { System.out.println(Thread.currentThread().getName()
+ ": uniqueInstance is not null now..."); //#5.2 } } } return uniqueInstance; } }
複製程式碼 複製程式碼
 1 public class TestSingleton {
 2     public static void main(final String[] args) throws InterruptedException {
 3         for (int i = 1; i <= 100000; i++) {
 4             final
Thread t1 = new Thread(new ThreadSingleton()); 5 t1.setName("thread" + i); 6 t1.start(); 7 } 8 } 9 10 public static class ThreadSingleton implements Runnable { 11 @Override 12 public void run() { 13 Singleton.getInstance(); 14 } 15 } 16 }
複製程式碼

這裡面的結果有可能會是:(沒有真正重現過,太難模擬了)

1 thread2: uniqueInstance is initalized...
2 thread3: uniqueInstance is initalized...
Singleton被例項化兩次了,和我們的單例模式設計期望值不一致:類永遠只被例項化一次.

原因分析:
1. thread2進入#1, 這時子執行緒的uniqueInstance都是為空的,thread2讓出CPU資源給thread3
2. thread3進入#1, 這時子執行緒的uniqueInstance都是為空的, thread3讓出CPO資源給thread2
3. thread2會依次執行#2,#3,#4, #5.1,最終在thread2裡面例項化了uniqueInstance。thread2執行完畢讓出CPO資源給thread3
4. thread3接著#1跑下去,跑到#3的時候,由於#1裡面拿到的uniqueInstance還是空(並沒有及時從thread2裡面拿到最新的),所以thread3仍然會執行#4,#5.1
5. 最後在thread2和thread3都例項化了uniqueInstance

例子2:用volatile修飾的uniqueInstance

這裡就不貼重複的程式碼了,因為只是加多一個volatile來修飾成員變數:uniqueInstance,

但是結果卻是正確的了, 其中一個可能結果:

thread2: uniqueInstance is initalized
thread3: uniqueInstance is not null now...

原因分析:

volatile(java5):可以保證多執行緒下的可見性;

讀volatile:每當子執行緒某一語句要用到volatile變數時,都會從主執行緒重新拷貝一份,這樣就保證子執行緒的會跟主執行緒的一致。

寫volatile: 每當子執行緒某一語句要寫volatile變數時,都會在讀完後同步到主執行緒去,這樣就保證主執行緒的變數及時更新。

1. thread2進入#1, 這時子執行緒的uniqueInstance都是為空的(java記憶體模型會從主執行緒拷貝一份uniqueInstance=null到子執行緒thread2),thread2讓出CPU資源給thread3
2. thread3進入#1, 這時子執行緒的uniqueInstance都是為空的(java記憶體模型會從主執行緒拷貝一份uniqueInstance=null到子執行緒thread2), thread3讓出CPO資源給thread2
3. thread2會依次執行#2,#3,#4, #5.1,最終在thread2裡面例項化了uniqueInstance(由於是volatile修飾的變數,會馬上同步到主執行緒的變數去)。thread2執行完畢讓出CPO資源給thread3
4. thread3接著#1跑下去,跑到#3的時候,會又一次從主執行緒拷貝一份uniqueInstance!=null回來,所以thread3就直接跑到了#5.2
5. 最後在thread3不再會重複例項化uniqueInstance了