單列模式與多線程

  在23個標準設計模式中,單例模式在應用中仍是很常見的,可是在多線程環境中,單例模式的使用有很是多的坑,使用好單例模式的一個原則:如何使單例模式在遇到多線程的環境中是安全的、正確的。下面分析幾種多線程的實現方式以及遇到的坑。設計模式

1、當即加載/餓漢模式

  當即加載:實用類的時候已經將對象建立完畢,常見的是直接new實例化,有「着急」,「急迫」的意思,所以也稱:「餓漢模式」。在調用方法前,已經實例化對象。代碼以下:安全

單例模式:多線程

public class SingleTon01 {
    private  static SingleTon01 instance=new SingleTon01();

    public SingleTon01() {
        super();
    }
    public static SingleTon01 getInstance(){
        return instance;
    }
}

線程:ide

public class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println(SingleTon01.getInstance().hashCode());
    }
}

測試類:測試

public class Run {
    public static void main(String[] args) {
        MyThread m1=new MyThread();
        MyThread m2=new MyThread();
        MyThread m3=new MyThread();
        MyThread m4=new MyThread();
        m1.start();
        m2.start();
        m3.start();
        m4.start();
    }
}

運行結果:優化

  全部線程的對象hashCode均是同樣的,證實是單例模式,but,該代碼的實現是優缺點的:不能有其餘實例變量,由於getInstance方法沒有同步,可能會出現線程安全問題。spa

2、延遲加載/懶漢模式

  延遲加載:在調用方法的時候,對象才被實例化,經常使用的實現方式就是在方法內部實例化對象。代碼以下:線程

單例模式:設計

public class SingleTon02 {
    private  static SingleTon02 instance;

    public SingleTon02() {
        super();
    }
    public static SingleTon02 getInstance(){
        if(null==instance){
            instance=new SingleTon02();
        }
        return instance;
    }
}

線程類:3d

public class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println(SingleTon02.getInstance().hashCode());
    }
}

測試類:

public class Run {
    public static void main(String[] args) {
        MyThread m1=new MyThread();
        MyThread m2=new MyThread();
        MyThread m3=new MyThread();
        MyThread m4=new MyThread();
        m1.start();
        m2.start();
        m3.start();
        m4.start();
    }
}

運行結果:

  從運行結果來看,控制檯打印了多個hashCode值,說明該實現方式在多線程的環境中是失敗的,如何解決呢?其實很簡單,讓方法同步便可,使用synchronized關鍵字。改進後代碼吐下:

public class SingleTon02 {
    private  static SingleTon02 instance;

    public SingleTon02() {
        super();
    }
    synchronized public static SingleTon02 getInstance(){
        try {
            if(null==instance){
                Thread.sleep(3000);//模擬業務處理
                instance=new SingleTon02();
            }
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return instance;
    }
}

再次運行:

  同步以後,證實該單例模式是正確的。可是,這種方式又帶來一種缺點,那就是效率問題,由於下一個線程必須須要等上一個線程釋放鎖以後才能執行,須要排隊執行,所以還能夠優化,那就是:嘗試同步代碼塊,針對重要代碼進行單獨同步,以提高效率。

  下面總結了一種使用DCL雙檢查鎖機制實現單例模式,該模式適用於在多線程環境中的延遲加載單例模式設計。代碼以下:

public class SingleTon03 {
    private volatile static SingleTon03 instance;

    public SingleTon03() {
        super();
    }
    public static SingleTon03 getInstance(){
        try {
            if(null==instance){
                Thread.sleep(3000);//模擬業務處理
                synchronized (SingleTon03.class) {
                    if(null==instance){
                        instance=new SingleTon03();
                    }
                }
            }
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return instance;
    }
}

這種方式既保證了線程的安全性,還保證了效率。

 3、使用靜態內之類實現單例模式

  前面的改進方式能夠實如今多線程的環境中實現單例模式,而且保證線程安全,那麼這種靜態內置類的方式也能夠實現一樣的效果。建立靜態內類,以下:

public class SingleTon04 {
    private static class SingleInner{
        private static SingleTon04 instance=new SingleTon04();
    }

    public SingleTon04() {
        super();
    }
    
    public static SingleTon04 getInstance(){
        return SingleInner.instance;
    }
}

線程類:

public class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println(SingleTon04.getInstance().hashCode());
    }
}

測試類同上

運行結果:

4、序列化與反序列化實現單例模式

  靜態內置類當然能夠實現單例模式,可是這裏有一個坑,那就是在遇到序列化和反序列化的時候,依然會出現問題,依然會出現多個實例化對象,代碼以下:

單例模式

public class SingleTon05 implements Serializable{

    private static final long serialVersionUID = 888888L;
    //內部類方式
    private static class SingleTonInner{
        private static final SingleTon05 instance=new SingleTon05();
    }
    public SingleTon05() {
        super();
    }
    public static SingleTon05 getInstance(){
        return SingleTonInner.instance;
    }
    
}

序列化運行類:

public class Run2 {
    public static void main(String[] args) {
        //
        try {
            SingleTon05 singleTon05=SingleTon05.getInstance();
            FileOutputStream out=new FileOutputStream(new File("singleton05.txt"));
            ObjectOutputStream objectOutputStream=new ObjectOutputStream(out);
            
            objectOutputStream.writeObject(singleTon05);
            objectOutputStream.close();
            out.close();
            //打印hashcode
            System.out.println(singleTon05.hashCode());
        } catch (IOException e) {
            e.printStackTrace();
        }
        //
        try {
            FileInputStream in=new FileInputStream(new File("singleton05.txt"));
            ObjectInputStream objectInputStream=new ObjectInputStream(in);
            
            SingleTon05 singleTon05=(SingleTon05)objectInputStream.readObject();
            objectInputStream.close();
            in.close();
            //打印hashcode
            System.out.println(singleTon05.hashCode());
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

運行結果:

  很明顯,寫入和讀出來的對象不是一個,顯然不符合單例模式的設計模式。序列化破壞了單例模式,固然,還有一種破壞單例模式的方式,那就是反射,單例模式中儘可能不要使用反射。呢麼問題來了,如何改進呢,其實很簡單,在序列化的時候在調用一個方法。改進以下:

public class SingleTon05 implements Serializable{

    private static final long serialVersionUID = 888888L;
    //內部類方式
    private static class SingleTonInner{
        private static final SingleTon05 instance=new SingleTon05();
    }
    public SingleTon05() {
        super();
    }
    public static SingleTon05 getInstance(){
        return SingleTonInner.instance;
    }
    protected Object readResolve()throws ObjectStreamException {
        System.out.println("調用了readResolve方法!");
        return SingleTonInner.instance;
    }
}

再次運行:

  序列化操做提供了一個很特別的鉤子(hook)-類中具備一個私有的被實例化的方法readresolve(),這個方法能夠確保類的開發人員在序列化將會返回怎樣的object上具備發言權。這樣就確保咱們在反序列化的時候返回的對象是同一個。

5、使用靜態代碼塊實現單例模式

  靜態代碼塊中的代碼執行實在實用類的時候加載,所以咱們能夠應用靜態代碼塊的這種特性來設計單例模式。代碼以下:

public class SingleTon06{

    private static  SingleTon06 instance=null;
    public SingleTon06() {
        super();
    }
    static{
        instance=new SingleTon06();
    }
    public static SingleTon06 getInstance(){
        return instance;
    }
}

線程類測試類同三,結果以下:

6、使用枚舉實現單例模式

   由於枚舉和靜態代碼塊的特性有類似之處,所以也可使用這種特性來設計單例模式,這種模式很是簡單,也推薦時使用。代碼以下:

public enum SingleTon07{
    INSTANCE;

    private SingleTon07() {
    }
    
    public static SingleTon07 getInstance(){
        return INSTANCE;
    }
    
}

測試運行類同上,結果以下:

  特色就是實現很是簡單。

相關文章
相關標籤/搜索