單例模式 分析 代碼優化

  單例模式是23種設計模式之一,是比較簡單的一種設計模式,它的目的是不管調用多少次,都返回同一個對象,它的特色是構造器私有化。java

  它分爲兩種結構,一種是懶漢式的,一種是餓漢式的,它們各有優缺點,咱們先從餓漢式看起,代碼以下:設計模式

public class Single {
    private static Single single = new Single();

    private Single() {

    }

    public Single getInstance() {
        return single;
    }

}

  經過上面的程序能夠看出來雖然咱們加載同一個對象的目的確實達到了,但當程序被加載的時候就會建立single這個對象,當這個類有多個這樣的方法時,咱們可能會用不到這個對象中大多數單例,就會形成對內存的浪費。因此就出現了懶漢式的單例模式,代碼以下:安全

public class Single {
    private static Single single = null;

    private Single() {

    }

    public Single getInstance() {
        if(single==null){
            single = new Single();
        }
        return single;
    }

}

  這樣,只有當咱們真正調用這個對象時它纔會被new出來,可是這樣是存在問題的。多線程

  當上面的第二段代碼在第一次加載的時候有兩個線程對其進行了調用,則會產生兩個不一樣的對象,因此是線程不安全的,這時候就會想到給這個方法加個鎖,加鎖以後的代碼以下:學習

public class Single {
    private static Single single = null;

    private Single() {

    }

    public synchronized Single getInstance() {
        if (single == null) {
            single = new Single();
        }
        return single;
    }

}

  這樣作確實作到了線程安全,可是當加鎖這個方花費的時間會很長,升級後的代碼以下:spa

public class Single {
    priate static Single single = null;

    private Single() {

    }

    public Single getInstance() {
        if (single == null) {
            synchronized (Single.class) {
                single = new Single();
            }
        }
        return single;
    }
}

  仔細觀察之後發現這樣並無鎖住,當第一次同時有兩個線程到達getInstance()方法if判斷時,其中有一個確定是阻塞的,當另一個執行完之後,阻塞這個線程是不會再判斷是否爲空的,仍是會建立一個對象的,這樣又有多個對象被產生了,再對其進行升級,獲得的代碼以下:線程

public class Single {
    private static Single single = null;

    private Single() {

    }

    public Single getInstance() {
        if (single == null) {
            synchronized (Single.class) {
                if (single == null) {
                    single = new Single();
                }
            }
        }
        return single;
    }
}

  這樣就不會產生上面的問題,並且也只鎖一次,由於第二次再執行這個方法時,會跳過if判斷,直接返回single,不會再被鎖,執行效率也會很高。設計

  但即便是這樣,也仍是有問題的,由於咱們不能肯定在內存中是先給對象賦值,仍是先建立了這個對象,因此第二個程序有可能獲得的是初始化一半了的對象,在jdk1.5以後,咱們能夠用volatile這個關鍵字來避免這種狀況,code

volatile能保證多線程之間的可見性(操做的是主內存,非線程內存),有效性(java不對其進行重排序),不能保證原子性(當讀出這個變量,還未寫回主內存時,別的線程讀取這個變量,取的是未改變以前的值)對象

下面的代碼加volatile關鍵字保證寫後能夠到主內存中,不加volatile可能會第一個線程將獲得的值放在線程內存中,沒有回寫到主內存,第二個線程就沒法獲取到第一個線程的結果,再new一個

代碼以下:

public class Single {
    private static volatile Single single = null;

    private Single() {

    }

    public Single getInstance() {
        if (single == null) {
            synchronized (Single.class) {
                if (single == null) {
                    single = new Single();
                }
            }
        }
        return single;
    }
}

  可是這種狀況不多使用,我在這裏只是爲了學習一下

最近有了一個新的寫法:以下

public class TestSingleton {

    private static class inner{
        private static TestSingleton singleton = new TestSingleton();
    }

    private TestSingleton(){

    }
     public static TestSingleton getSingle(){
         return inner.singleton;
     }

}

這種方法很不錯的,這樣避免了在加載類的時候就初始化,達到了懶加載的目的

比下面這種方式好一些

public class TestSingle2 {
    
    private static TestSingle2 testSingleton ;
    
    static {
        testSingleton = new TestSingle2();
    }
    
    private TestSingle2(){
        
    }
    
    public static TestSingle2 getSingleton(){
        return  testSingleton;
    }
}

這種方式就沒有實現懶加載,

另一種就是使用玫舉的特性生成單例(玫舉是線程安全的,final的,構造方法是private且只調一次),代碼以下: 

public class SingletonEnumTest {

    private SingletonEnumTest(){

    }

    private enum Singleton{
        INSTANCE;

        private SingletonEnumTest instance;

        private Singleton(){
            instance = new SingletonEnumTest();
        }

        public SingletonEnumTest getInstance(){
            return instance;
        }
    }

    public static SingletonEnumTest getInstance(){
        return Singleton.INSTANCE.getInstance();
    }

    public static void main(String[] args) {
        IntStream.range(0,100).forEach(i->{
            new Thread(()->{
                System.out.println(SingletonEnumTest.getInstance());
            }).start();
        });
    }
} 
相關文章
相關標籤/搜索