一、单例模式 - 6种实现

设计模式

一、单例模式

特点:

  1. 单例类只能有一个实例

  2. 单例类必须自己创建自己的唯一实例

  3. 单例类必须给所有其他对象提供这一实例

解决问题:

  1. 一个全局使用的类频繁地创建与销毁

 

1. 懒汉模式

public class SingletonOne {
    private SingletonOne() {}
    private static SingletonOne instance = null;
    public static SingletonOne getInstance() {
        // 此处存在线程安全
        if (instance == null) {    
            instance = new SingletonOne();
        }
        return instance;
    }
}

 

2. 饿汉模式

public class Singleton2 {
    private Singleton2() {}
    private static Singleton2 instance = new Singleton2();  // 类加载较慢
    public static Singleton2 getInstance() {
        return Singleton2.instance;
    }
}

 

3. 双重检查模式 (DCL)

public class Singleton3 {
    private Singleton3() {}
    private static Singleton3 instance = null;
    public static Singleton3 getInstance() {
        if (null == instance) {
            // JVM编译器的指令重排 导致 线程安全
            synchronized (Singleton3.class) {
                if (instance== null) {
                    instance= new Singleton3();
                }
            }
        }
        return instance;
    }
}

 

4. 双重检查模式 - 优化版

public class Singleton4 {
    private Singleton4() {}
    // volatile 关键字 禁止指令重排序
    private volatile static Singleton4 instance = null;
    public static Singleton4 getInstance() {
        if (null == instance) {
            synchronized (Singleton4.class) {
                if (instance== null) {
                    instance= new Singleton4();
                }
            }
        }
        return instance;
    }
}

 

5. 静态内部类

/**
 * 1.从外部无法访问静态内部类LazyHolder,只有当调用Singleton.getInstance方法的时候,才能得到单例对象INSTANCE。
 * 2.INSTANCE对象初始化的时机并不是在单例类Singleton被加载的时候,而是在调用getInstance方法,使得静态内部类LazyHolder被加载的时候。
 *   因此这种实现方式是利用classloader的加载机制来实现懒加载,并保证构建单例的线程安全。
 */
public class Singleton5 {
    private Singleton5() {}
    private static class LazyHolder {
        private static final Singleton5 INSTANCE = new Singleton5();
    }
    public static Singleton5 getInstance() {
        return LazyHolder.INSTANCE;
    }
}

5.1 反射打破单例模式的约束

//获得构造器
Constructor con = Singleton5.class.getDeclaredConstructor();
//设置为可访问
con.setAccessible(true);
//构造两个不同的对象
Singleton5 singleton1 = (Singleton5)con.newInstance();
Singleton5 singleton2 = (Singleton5)con.newInstance();
//验证是否是不同对象
System.out.println(singleton1);
System.out.println(singleton2);
​
// 结果
singleton.Singleton5@4554617c
singleton.Singleton5@74a14482

 

6. 枚举

// 防止利用反射强行构建单例对象
public enum Singleton6 {
    INSTANCE;
    public void doSomething(){
    }
}

 

7. 总结

实现线程安全懒加载防止反射构建
懒汉模式
饿汉模式
双重检查模式
静态内部类
枚举

注意:一般情况下,不建议使用第 1 种和第 2 种懒汉方式,建议使用第 4 种饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用第 5 种静态内部类方式。如果涉及到反序列化创建对象时,可以尝试使用第 6 种枚举方式。

 

8. 补充

  1. volatile关键字不但可以防止指令重排,也可以保证线程访问的变量值是主内存中的最新值。有关volatile的详细原理,我在以后的漫画中会专门讲解。

  1. 使用枚举实现的单例模式,不但可以防止利用反射强行构建单例对象,而且可以在枚举类对象被反序列化的时候,保证反序列的返回结果是同一对象。

  1. 对于其他方式实现的单例模式,如果既想要做到可序列化,又想要反序列化为同一对象,则必须实现readResolve方法。

posted @ 2020-06-19 17:33  剑豪索隆  阅读(133)  评论(1编辑  收藏  举报