單例模式中的懶漢式以及線程安全性問題

先看代碼:java

package com.roocon.thread.t5;

public class Singleton2 {

    private Singleton2(){

    }

    private static Singleton2 instance;

    public static Singleton2 getInstance(){
        if(instance == null) {//1:讀取instance的值
            instance = new Singleton2();//2: 實例化instance
        }
        return instance;
    }

}
package com.roocon.thread.t5;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MultiThreadMain {
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newFixedThreadPool(20);
        for (int i = 0; i< 20; i++) {
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+":"+Singleton2.getInstance());
                }
            });
        }
     threadPool.shutdown();
} }

運行結果:安全

pool-1-thread-4:com.roocon.thread.t5.Singleton2@6519891a
pool-1-thread-14:com.roocon.thread.t5.Singleton2@6519891a
pool-1-thread-10:com.roocon.thread.t5.Singleton2@6519891a
pool-1-thread-8:com.roocon.thread.t5.Singleton2@6519891a
pool-1-thread-5:com.roocon.thread.t5.Singleton2@6519891a
pool-1-thread-12:com.roocon.thread.t5.Singleton2@6519891a
pool-1-thread-1:com.roocon.thread.t5.Singleton2@6519891a
pool-1-thread-9:com.roocon.thread.t5.Singleton2@6519891a
pool-1-thread-6:com.roocon.thread.t5.Singleton2@6519891a
pool-1-thread-2:com.roocon.thread.t5.Singleton2@6519891a
pool-1-thread-16:com.roocon.thread.t5.Singleton2@6519891a
pool-1-thread-3:com.roocon.thread.t5.Singleton2@1c208db1
pool-1-thread-17:com.roocon.thread.t5.Singleton2@6519891a
pool-1-thread-13:com.roocon.thread.t5.Singleton2@6519891a
pool-1-thread-18:com.roocon.thread.t5.Singleton2@6519891a
pool-1-thread-7:com.roocon.thread.t5.Singleton2@6519891a
pool-1-thread-20:com.roocon.thread.t5.Singleton2@6519891a
pool-1-thread-11:com.roocon.thread.t5.Singleton2@6519891a
pool-1-thread-15:com.roocon.thread.t5.Singleton2@6519891a
pool-1-thread-19:com.roocon.thread.t5.Singleton2@6519891a

發現,有個實例是Singleton2@1c208db1,也就說明,返回的不是同一個實例。這就是所謂的線程安全問題。ide

解釋緣由:對於以上代碼註釋部分,若是此時有兩個線程,線程A執行到1處,讀取了instance爲null,而後cpu就被線程B搶去了,此時,線程A尚未對instance進行實例化。spa

所以,線程B讀取instance時仍然爲null,因而,它對instance進行實例化了。而後,cpu就被線程A搶去了。此時,線程A因爲已經讀取了instance的值而且認爲它爲null,因此,線程

再次對instance進行實例化。因此,線程A和線程B返回的不是同一個實例。code

 

那麼,如何解決呢?對象

1.在方法前面加synchronized修飾。這樣確定不會再有線程安全問題。blog

package com.roocon.thread.t5;

public class Singleton2 {

    private Singleton2(){

    }

    private static Singleton2 instance;

    public static synchronized Singleton2 getInstance(){
        if(instance == null) {//1
            instance = new Singleton2();//2
        }
        return instance;
    }

}

可是,這種解決方式,假若有100個線程同時執行,那麼,每次去執行getInstance方法時都要先得到鎖再去執行方法體,若是沒有鎖,就要等待,耗時長,感受像是變成了串行處理。所以,嘗試其餘更好的處理方式。排序

2. 加同步代碼塊,減小鎖的顆粒大小。咱們發現,只有第一次instance爲null的時候,纔去建立實例,而判斷instance是否爲null是讀的操做,不可能存在線程安全問題,所以,咱們只須要對建立實例的代碼進行同步代碼塊的處理,也就是所謂的對可能出現線程安全的代碼進行同步代碼塊的處理。內存

package com.roocon.thread.t5;

public class Singleton2 {

    private Singleton2(){

    }

    private static Singleton2 instance;

    public static Singleton2 getInstance(){
        if(instance == null) {
            synchronized (Singleton2.class){
                instance = new Singleton2();
            }
        }
        return instance;
    }

}

可是,這樣處理就沒有問題了嗎?一樣的原理,線程A和線程B,線程A讀取instance值爲null,此時cpu被線程B搶去了,線程B再來判斷instance值爲null,因而,它開始執行同步代碼塊中的代碼,對instance進行實例化。此時,線程A得到cpu,因爲線程A以前已經判斷instance值爲null,因而開始執行它後面的同步代碼塊代碼。它也會去對instance進行實例化。

這樣就致使了仍是會建立兩個不同的實例。

那麼,如何解決上面的問題。

很簡單,在同步代碼塊中instance實例化以前進行判斷,若是instance爲null,纔對其進行實例化。這樣,就能保證instance只會實例化一次了。也就是所謂的雙重檢查加鎖機制。

再次分析上面的場景:

線程A和線程B,線程A讀取instance值爲null,此時cpu被線程B搶去了,線程B再來判斷instance值爲null。因而,它開始執行同步代碼塊代碼,對instance進行了實例化。這是線程A得到cpu執行權,當線程A去執行同步代碼塊中的代碼時,它再去判斷instance的值,因爲線程B執行完後已經將這個共享資源instance實例化了,因此instance再也不爲null,因此,線程A就不會再次實行實例化代碼了。

package com.roocon.thread.t5;

public class Singleton2 {

    private Singleton2(){

    }

    private static Singleton2 instance;

    public static synchronized Singleton2 getInstance(){
        if(instance == null) {
            synchronized (Singleton2.class){
                if (instance == null){
                    instance = new Singleton2();
                }
            }
        }
        return instance;
    }

}

可是,雙重檢查加鎖並不代碼百分百必定沒有線程安全問題了。由於,這裏會涉及到一個指令重排序問題。instance = new Singleton2()其實能夠分爲下面的步驟:

1.申請一塊內存空間;

2.在這塊空間裏實例化對象;

3.instance的引用指向這塊空間地址;

指令重排序存在的問題是:

對於以上步驟,指令重排序頗有可能不是按上面123步驟依次執行的。好比,先執行1申請一塊內存空間,而後執行3步驟,instance的引用去指向剛剛申請的內存空間地址,那麼,當它再去執行2步驟,判斷instance時,因爲instance已經指向了某一地址,它就不會再爲null了,所以,也就不會實例化對象了。這就是所謂的指令重排序安全問題。那麼,如何解決這個問題呢?

加上volatile關鍵字,由於volatile能夠禁止指令重排序。

package com.roocon.thread.t5;

public class Singleton2 {

    private Singleton2(){

    }

    private static volatile Singleton2 instance;

    public static Singleton2 getInstance(){
        if(instance == null) {
            synchronized (Singleton2.class){
                if (instance == null){
                    instance = new Singleton2();
                }
            }
        }
        return instance;
    }

}
相關文章
相關標籤/搜索