單例模式 分析 代碼優化


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

  它分為兩種結構,一種是懶漢式的,一種是餓漢式的,它們各有優缺點,我們先從餓漢式看起,代碼如下:

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;
    }

}

  這樣做確實做到了線程安全,但是當加鎖這個方花費的時間會很長,升級后的代碼如下:

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這個關鍵字來避免這種情況,

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();
        });
    }
} 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM