带你搞懂单例模式

100 阅读3分钟

前言

我们总是会遇到一个问题spring的单例模式,那么什么是单例模式呢,今天就来带大家了解一下。

单例模式:

单例模式有懒汉式和饿汉式:

  1. 懒汉式单例模式:在类加载时不初始化。

  2. 饿汉式单例模式:在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快。

一、普通懒汉式–线程不安全

public class SingletonDemo1 {  
    //实例化本身  
    private static SingletonDemo1 instance;  
    //私有构造方法  
    private SingletonDemo1(){}  
    //提供外部访问接口  
    public static SingletonDemo1 getInstance(){  
        //判断对象是否为空,为空则实例化  
        if (instance == null) {  
            instance = new SingletonDemo1();  
        }  
        //对象不为空则返回该对象  
        return instance;  
    }  
}

注:这种写法lazy loading很明显,但是致命的是在多线程不能正常工作,因为在多线程下有可能造成实例化多次的情况,此时就不再是单例了。

二、同步方法懒汉式

public class SingletonDemo2 {  
    private static SingletonDemo2 instance;  
    private SingletonDemo2(){}  
    //使用同步方法,防止两个线程同时访问该方法,进而避免多次实例化的情况出现  
    public static synchronized SingletonDemo2 getInstance(){  
        if (instance == null) {  
            instance = new SingletonDemo2();  
        }  
        return instance;  
    }  
}

注:这种写法在getInstance()方法中加入了synchronized锁。能够在多线程中很好的工作,而且看起来它也具备很好的lazy loading,但是效率很低(因为锁),并且大多数情况下不需要同步。

三、普通饿汉式

public class SingletonDemo3 {  
    //饿汉式写法,在类加载时就完成实例化,可以避免线程安全问题  
    private static SingletonDemo3 instance = new SingletonDemo3();  
    private SingletonDemo3(){}  
    public static SingletonDemo3 getInstance(){  
        return instance;  
    }  
}

注:这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,这时候初始化instance显然没有达到lazy loading的效果。

四、嵌入静态代码块的饿汉式

public class SingletonDemo4 {  
    private static SingletonDemo4 instance = null;  
    //使用静态代码块完成实例化,  
    //由于静态代码块只在类加载的时候执行一次,所以能够保证单例  
    static{  
        instance = new SingletonDemo4();  
    }  
    private SingletonDemo4(){}  
    public static SingletonDemo4 getInstance(){  
        return instance;  
    }  
}

注:表面上看起来差别挺大,其实和第2.3中的饿汉式方式差不多,都是在类初始化即实例化instance

五、使用静态内部类的饿汉式

public class SingletonDemo5 {  
    //使用内部类进行实例化  
    private static class SingletonHolder{  
        private static final SingletonDemo5 instance = new SingletonDemo5();  
    }  
    private SingletonDemo5(){}  
    public static final SingletonDemo5 getInsatance(){  
        //当主动调用内部类的静态方法时装载对象  
        return SingletonHolder.instance;  
    }  
}

注:这种方式同样利用了classloder的机制来保证初始化instance时只有一个线程,它跟第三和四不同的是(很细微的差别):

三和四是只要Singleton类被装载了,那么instance就会被实例化(没有达到lazy loading效果),而这种方式是Singleton类被装载了,instance不一定被初始化。

因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。

想象一下,如果实例化instance很消耗资源,我想让他延迟加载,另外一方面,我不希望在Singleton类加载时就实例化,因为我不能确保Singleton类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化instance显然是不合适的。这个时候,这种方式相比第三和第四种方法就显得更合理。

六、使用枚举的饿汉式

public enum SingletonDemo6 {
    instance;
    public void whateverMethod(){
    }
}

七、双重校验锁的懒汉式

public class SingletonDemo7 {  
    private volatile static SingletonDemo7 singletonDemo7;  
    private SingletonDemo7(){}  
    public static SingletonDemo7 getSingletonDemo7(){  
        //第一重锁,判断是否为空  
        if (singletonDemo7 == null) {  
            //使用同步代码块,解决多线程问题  
            synchronized (SingletonDemo7.class) {  
               //再次进行判断是否为空  
               if (singletonDemo7 == null) {  
                   singletonDemo7 = new SingletonDemo7();  
               }  
            }  
         }  
     return singletonDemo7;  
   }  
}