• 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏吧

单例创建的几种方式

开发技术 开发技术 2周前 (09-04) 17次浏览

单例创建的几种方式

一、饿汉式

优点:线程安全。由于示例对象使用static修饰,类初始的时候就创建了对象,被加载到了内存,不存在多线程问题。

缺点:即使没有使用这个对象,内存依然会占空间,浪费内存。

public class Singleton{

    private static Singleton instance = new Singleton();

    private Singleton() {}

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

二、懒汉式

优点:使用的时候才创建,省内存空间。

缺点:线程不安全。多线程下可能会创建多个实例(问题很严重)。

public class Singleton{

    private static Singleton instance;

    private Singleton() {}

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

线程安全的做法volatile禁止指令重排序保证线程间可见性,加双重锁双重校验保证线程安全。

public class Singleton {

    private static volatile Singleton instance;

    private Singleton() {}

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

如果这里不加volatile变量修饰,是有问题的。原因是new对象的时候,分为三个步骤:
分配内存空间,初始化对象,将内存地址赋值给变量。但是在操作上执行时,有可能会指令重排序,即这三个步骤执行时没有固定的顺序。如线程A在还没有初始化对象时,先将内存地址赋值给了变量,此时线程B进来后,执行到第一个校验null的地方,发现变量不为null,就会直接返回这个还没有初始化完成的对象

三、静态内部类

优点:线程安全,调用效率高,可以延时加载

public class Singleton {

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

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

四、枚举

优点:线程安全,调用效率高,可以天然的防止反射和反序列化调用

缺点:不能延时加载

public enum Single {
    
    INSTANCE;
    
    public void operation(){
        //自己的操作
    }
}

枚举为能做到防止反射,克隆及序列化对单例的破坏,除了枚举方式创建的单例,单例类模式不一定创建的对象只会有一个

破坏单例模式的方式:反射,反序列化,克隆

解决办法:

//构造器改造,防止反射创建不同的单例对象
private Singleton() {
    // 禁用反射,防止反射获取多个对象的漏洞
    if (null != instance) {
    	throw new RuntimeException();
    }
}

/**
* 防止反序列化获取多个对象
* 无论是实现Serializable接口,或是Externalizable接口,当从I/O流中读取对象时,  readResolve()方法都会被调用到
* 实际上就是用readResolve()中返回的对象直接替换在反序列化过程中创建的对象。
*/
private Object readResolve() {
    return instance;
}

//防止克隆破坏,重写clone(),直接返回单例对象
@Override
protected Object clone() {
    return instance;
}

喜欢 (0)