單例模式的幾種實現方式

什麼是單例模式:

  當系統中某個類對象只須要實例化一次的時候,咱們就用單例來實現。因此單例模式就是用來建立獨一無二,只能有一個實例的對象的一直實現方式。數據庫

常見的使用場景:

  好比線程池,緩存,鏈接數據庫的Connection等等。緩存

單例模式的幾種實現方式:

1,餓漢式安全

package singleton;

/**
 * @ClassName SingletonDemo1
 * @Description 餓漢式
 * 類加載到內存後,就實例化一個單例對象,JVM保證線程安全
 * 惟一缺點:無論用不用都會進行加載,可是影響不大,其實是最適用的一種方式
 * @Author liuyi
 * @Date 2020/6/7 12:22
 * @Version 1.0
 */
public class SingletonDemo1 {
    //兩種方式初始化實例,兩種方式的效果是同樣的
    //靜態常量方式
//    private static final SingletonDemo1 instance = new SingletonDemo1();
    //靜態塊方式
    private static final SingletonDemo1 instance;
    static {
        instance = new SingletonDemo1();
    }
    private SingletonDemo1(){

    }
    public static SingletonDemo1 getInstance(){
        return instance;
    }

    public static void main(String[] args) {
        SingletonDemo1 instance1 = SingletonDemo1.getInstance();
        SingletonDemo1 instance2 = SingletonDemo1.getInstance();
        System.out.println(instance1==instance2);
        //返回的結果爲true,說明無論取多少次都是同一個實例
    }
}

2,懶漢式多線程

package singleton;

/**
 * @ClassName SingletonDemo2
 * @Description 懶漢式
 * 須要使用該對象的時候再去實例化
 * 缺點:會產生線程安全問題
 * @Author liuyi
 * @Date 2020/6/7 13:23
 * @Version 1.0
 */
public class SingletonDemo2 {
    private static SingletonDemo2 instance;

    private SingletonDemo2(){

    }

    public static SingletonDemo2 getInstance(){
        if(instance==null){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            instance = new SingletonDemo2();
        }
        return instance;
    }

    public static void main(String[] args) {
        //爲何說線程不安全
        //由於使用了全局的靜態變量,會形成多線程訪問的時候會產生不惟一的實例
        for (int i = 0; i <100 ; i++) {
            new Thread(()->{
                System.out.println(getInstance());
            }).start();
        }
        //打印的對象徹底亂了,根本不是同一個實例
    }
}

3,餓漢式(加鎖)ide

package singleton;

/**
 * @ClassName SingletonDemo2
 * @Description 懶漢式(加鎖)
 * 缺點:效率低
 * @Author liuyi
 * @Date 2020/6/7 13:23
 * @Version 1.0
 */
public class SingletonDemo3 {
    private static SingletonDemo3 instance;

    private SingletonDemo3(){

    }

    public synchronized static SingletonDemo3 getInstance(){
        if(instance==null){
            instance = new SingletonDemo3();
        }
        return instance;
    }

    public static void main(String[] args) {
        //加了鎖解決了懶漢式的線程不安全問題,可是這樣效率就會明顯下降
        for (int i = 0; i <100 ; i++) {
            new Thread(()->{
                System.out.println(getInstance());
            }).start();
        }
    }
}

4,雙重檢查測試

package singleton;

/**
 * @ClassName SingletonDemo2
 * @Description 雙重檢查(比較完美的寫法)
 * @Author liuyi
 * @Date 2020/6/7 13:23
 * @Version 1.0
 */
public class SingletonDemo4 {
    //必須加volatile關鍵字,防止指令重排
    private static volatile SingletonDemo4 instance;

    private SingletonDemo4(){

    }

    public synchronized static SingletonDemo4 getInstance(){
        //爲何要進行雙重檢查
        //好比兩個線程同時進入該方法,都拿到instance爲空,其中一個拿到鎖並new了一個實例,
        //此時另一個線程它並不知道你已經new了實例,因此當它拿到鎖以後會繼續new一個實例
        //因此若是在鎖裏面繼續判斷一次是頗有必需要的
        if(instance==null){
            synchronized (SingletonDemo4.class){
                if(instance==null){
                    instance = new SingletonDemo4();
                }
            }
        }
        return instance;
    }

    public static void main(String[] args) {
        for (int i = 0; i <100 ; i++) {
            new Thread(()->{
                System.out.println(getInstance());
            }).start();
        }
    }
}

5,靜態內部類方式spa

package singleton;

/**
 * @ClassName SingletonDemo5
 * @Description 靜態內部類方式
 * JVM保證線程安全
 * 加載外部類是不會加載內部類,實現了懶加載
 * 最完美的寫法
 * @Author liuyi
 * @Date 2020/6/7 13:52
 * @Version 1.0
 */
public class SingletonDemo5 {

    private SingletonDemo5(){

    }
    private static class SingletonDemo5Inside{
        private static final SingletonDemo5 instance = new SingletonDemo5();
    }

    public static SingletonDemo5 getInstance(){
        return SingletonDemo5Inside.instance;
    }

    public static void main(String[] args) {
        for (int i = 0; i <100 ; i++) {
            new Thread(()->{
                System.out.println(SingletonDemo5.getInstance());
            }).start();
        }
    }
}

6,枚舉方式線程

package singleton;

/**
 * @Author liuyi
 * @Description 枚舉單例
 * 不只能夠解決線程同步,還能夠防止反序列化(由於枚舉類沒有構造方法)
 * @Date 17:28 2020/6/7
 * @Param  
 * @return 
 **/
public enum SingletonDemo6 {

    instance;

    public void test(){
        System.out.println("測試測試");
    }
    public static void main(String[] args) {
        instance.test();
        for (int i = 0; i <100 ; i++) {
            new Thread(()->{
                System.out.println(SingletonDemo6.instance);
            }).start();
        }
    }
}

總結:

  這幾種實現單例的方式中,枚舉單例是最完美的,由於枚舉單例能夠防止反序列話,也能夠防止經過反射的方式去建立實例,可是實際運用中最適用的仍是餓漢式。由於既然你使用了單例,爲何還要用反射呢,這樣就屬於搞破壞了。code

相關文章
相關標籤/搜索