设计模式--单例模式的实现方式

90%程序员第一个听过的设计模式一定是单例模式,我觉得是因为其实现简单,理解起来容易,才如此流行。正因为它的知名度和流行度,单例模式的实现方式有很多,而且一直在更新。我们今天就讨论下目前主流生成单例模式的方式。

 我们听过最多的单例模式实现方式是饿汉式,懒汉式,但其实还有静态内部类等其他方式。

1.饿汉式

public class Singleton {

    private static Singleton singleton = new Singleton();

    private Singleton(){}

    public static Singleton getInstance(){
        return singleton;
    }

}

这种方式很多人都会写,但这样写单例模式有利有弊,优点是不用担心并发,静态变量只会初始化一次,缺点是如果引用了这个类的其他静态变量,就会创建该类的对象,但你可能永远都不会使用这个对象,造成内存的浪费,所以不推荐使用这种方式

2.懒汉式

public class Singleton {
    
    private static  Singleton singleton;

    private Singleton(){}

    public static Singleton  getInstance(){
        if(singleton == null){
            singleton = new Singleton();
        }
        return singleton;
    }
    
}

懒汉式就不会有这个问题

刚开始学习 java 的时候,可能会这样写,但随着学习的深入,你会发现这样写有并发问题,试想线程1 刚判断完 singleton 为 null,准备去新建实例,线程2 也走到了判断 singleton 这一步,此时 singleton 还是空,线程2也新建了一个实例,这就不能保证单例了,因此懒汉式我们都是用下面这种方式

3.懒汉式(同步锁)

public class Singleton {

    private static  Singleton singleton;

    private Singleton(){}

    public static Singleton  getInstance(){
        if(singleton == null){
            synchronized (Singleton.class){
                if(singleton == null){
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }

}

这种方式通过加同步锁保证对象的唯一性,这里我们判断了两次 singleton,原因是,如果线程1,2都在外层判断了 singleton 为空,线程1先拿到了同步锁,创建了一个新对象,线程2等待,线程1完成后,线程2也拿到了同步锁,如果内层不再判断 singleton 是否为空,线程2就会再次创建一个对象,这就无法保证对象的唯一性了。由于加了同步锁,这种方式在高并发下也能保证对象的唯一性。但这种写法还是会有一个问题,jvm 创建一个对象执行三步

1)堆内存开辟内存空间

2)在堆内存中实例化SingleTon里面的各个参数

3)对象指向堆内存空间

由于 jvm会优化指令,可能在执行第二步之前先执行了第三步,另一个线程走到这一步看到对象是非空的,直接拿来用,就会发生异常。后来 JDK1.5后,官方也发现了这个问题就发明了 volatile 关键字,下面这样写就没问题了

public class DCLSafeSingleton {
    
    //添加 volatile 关键字,防止指令重排
    private static volatile DCLSafeSingleton singleton;

    private DCLSafeSingleton() {}

    public static DCLSafeSingleton getInstance() {
        if (singleton == null) {
            synchronized (DCLSafeSingleton.class) {
                if (singleton == null) {
                    singleton = new DCLSafeSingleton();
                }
            }
        }
        return singleton;
    }
    
}

4.静态内部类

public class InnerClassSingleton{

    public static InnerClassSingleton getInstance() {
        return Instance.singleton;
    }

    private InnerClassSingleton(){}

    private static class Instance {
        static InnerClassSingleton singleton = new InnerClassSingleton();

    }

}

这种是算是比较好的单例模式写法了,静态内部类只加载一次,保证了线程安全,只有调用 getInstance 方法才会加载内部类,创建实例,不会造成内存的浪费。但这种方式有个缺点,由于是内部类形式,所以创建对象时无法传参。虽然这种方法看上去比较安全了,但可能存在反射攻击或反序列化攻击。如下面代码

反射攻击

public static void main(String[] args) throws Exception {
        InnerClassSingleton singleton = InnerClassSingleton.getInstance();
        Constructor<InnerClassSingleton> constructor = InnerClassSingleton.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        InnerClassSingleton newSingleton = constructor.newInstance();
        System.out.println(singleton == newSingleton);
    }

运行结果是 false

反序列化攻击

先引入依赖

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.8.1</version>
</dependency>

代码

public class Singleton implements Serializable {

    private static class Instance{
        private static Singleton instance = new Singleton();
    }

    private Singleton() {}

    public static Singleton getInstance() {
        return Instance.instance;
    }

    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        byte[] serialize = SerializationUtils.serialize(instance);
        Singleton newInstance = SerializationUtils.deserialize(serialize);
        System.out.println(instance == newInstance);
    }

}

运行结果也是 false

5.枚举法

《Effective java 》这本书写这是实现单例模式最好的方法。以上优点它都有,还不用担心有反射攻击或反序列化攻击,如下图

 调用方法:

public enum  EnumSingleton {
    
    INSTANCE;
    
    public void getSomething() {
        System.out.println("getSomething");
    }

    //调用方式
    public static void main(String[] args) {
        EnumSingleton.INSTANCE.getSomething();
    }

}

以上就是单例模式主要的创建方式啦,园友们可以根据具体业务场景进行选择合适的实现方式。

猜你喜欢

转载自www.cnblogs.com/fightingting/p/10608996.html