设计模式的学习总结-单例模式详解

一、单例模式的几种创建方式

1.饿汉式单例。 特点:线程安全,使用效率高,但是不能延迟加载

2.懒汉式单例。特点:线程安全,调用效率不高,但是能延迟加载

3.Double CheckLock实现单例(懒汉式)。特点:DCL也就是双重锁判断机制,基于JVM底层模型

4.静态内部类。特点:线程安全,调用效率高,可以延时加载

5.枚举式单例。特点:线程安全,调用效率高,可以天然的防止反射和反序列化调用

二、单例模式详解

1.饿汉式单例

/**
 * 饿汉式单例
 */
public class HungrySingle {

    //饿汉式:在类初始化的时候就分配内存,并创建对象
    private static HungrySingle hungrySingle = new HungrySingle();

    private HungrySingle(){
        //构造方法加判断,防止反射攻击
        if( hungrySingle != null){
            throw new RuntimeException("单例构造器禁止反射调用");
        }
    }

    public static HungrySingle getInstance(){
        return hungrySingle;
    }
}

    public static void main(String[] args){

        try {
            //调用者装逼,通过反射获取对象,破坏单例
            Class clazz = HungrySingle.class;
            Constructor c = clazz.getDeclaredConstructor();
            c.setAccessible(true);

            HungrySingle h1 = (HungrySingle) c.newInstance();

            //常规获取对象
            HungrySingle h2 = HungrySingle.getInstance();

            System.out.println("h1="+h1);
            System.out.println("h2="+h2);

            System.out.println(h1 == h2);

        }catch (Exception e){
            e.printStackTrace();
        }
    }

 2.懒汉式单例

/**
*懒汉式单例
*/
public class LazySingle {

    //懒汉式:在运行时才创建对象,懒加载
    private static LazySingle lazySingle = null;

    private LazySingle(){
        //构造方法加判断,防止反射攻击
        if (lazySingle != null){
            throw new RuntimeException("单例构造器禁止反射调用");
        }
    }

    public synchronized static LazySingle getInstance(){ //锁住整个类,效率底
        if (lazySingle == null) {
                lazySingle = new LazySingle();
        }
        return lazySingle;
    }
}

3.Double CheckLock实现单例(懒汉式)

public class LazySingle {

    private static LazySingle lazySingle = null;

    private LazySingle(){
        //构造方法加判断,防止反射攻击
        if (lazySingle != null){
            throw new RuntimeException("单例构造器禁止反射调用");
        }
    }

    public  static LazySingle getInstance(){
        //双重锁判断
        if (lazySingle == null) {

            synchronized (LazySingle.class) {

                if (lazySingle == null) {
                    lazySingle = new LazySingle();
                }
            }
        }
        return lazySingle;
    }
    //在反序列化时,用这个方法返回的对象引用替代readObject新建的引用
    private Object readResolve() {
        return lazySingle;
    }
}

设计模式的学习总结-单例模式详解_第1张图片

4.静态内部类

public class InsideSingle implements Serializable {

    //volatile属性,是类加载顺序可见
    private volatile static InsideSingle insideSingle = null;

    private InsideSingle(){
        //构造方法加判断,防止反射攻击
        if (insideSingle != null){
            throw new RuntimeException("单例构造器禁止反射调用");
        }
    }
    /**
     * 静态内部类创建对象
     */
    private static class getInside{
        private static InsideSingle insideSingle = new InsideSingle();
    }
    public static InsideSingle getInstance(){
        return getInside.insideSingle;
    }
    //在反序列化时,用这个方法返回的对象引用替代readObject新建的引用
    private Object readResolve() {
        return getInstance();
    }
}

 

 静态内部类的优点:外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化insideSingle,故而不占内存。即当InsideSingle第一次被加载时,并不需要去加载getInside,只有当getInstance()方法第一次被调用时,才会去初始化insideSingle,第一次调用getInstance()方法会导致虚拟机加载getInside类,这种方法不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。

这里:如何实现线程安全的?首先,我们先了解下类的加载机制。详细解释自行去了解吧

1.在内存中开辟一个空间。

2.创建一个对象的地址。

3.把创建的地址的引用指向所开辟内存的空间。

4.对象创建并赋值。

有时候第二步和第三步的顺序并不确定,所以我们可以加上 volatile 保证类加载顺序的可见性。

5.枚举式单例

//常量中去使用,常量不就是用来大家都能够共用吗?
//通常在通用API中使用
public enum EnumSingle {
    INSTANCE;
    private Object data;
    public Object getData() {
        return data;
    }
    public void setData(Object data) {
        this.data = data;
    }
    public static EnumSingle getInstance(){
        return INSTANCE;
    }
}

枚举单例在jvm内部就有效的防止了反射破坏情况。同时序列化也没有多创建对象。

Spring中的做法,就是用这种注册式单例

你可能感兴趣的:(设计模式-单例模式,java)