单例模式

一.定义

单例模式,类都是单例的,一个类最多只有一个实例对象。

二.实现

为实现单例模式,类的构造器须设置为私有的,然后通过方法取获取类的实例

1.懒汉式

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

简单的一种方式,第一次调用getInstance()方法时产生唯一实例

优:简单

缺:在多线程下,第一次初始化时可能多个线程同时判断出instance为null,产生了多个实例,破坏单例

2.懒汉式(多线程)

当涉及多线程的时候,懒汉式就可能会产生多个实例。可以直接在getInstance()方法上加synchronize,但效率太低,一般用以下方式

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

第一次调用getInstance()方法时产生唯一实例

优:适用于多线程

缺:可能出现问题,详细如下

instance = new Singleton() 并不是原子操作,可以分为以下几步:

1.为对象分配内存

2.初始化一个Singleton对象

3.将instnce引用指向对象地址

由于重排序的原因,可能产生1-2-3或1-3-2的执行顺序,若3先执行再执行2,jvm去初始化对象,在初始化的过程中,另一个新的线程调用getInstance()方法,然后判断第一个instance==null时,为false,直接返回instance,而返回的instance对象是一个未初始化完成的对象,使用时可能会出错。

另外还有个线程可见性问题,一个线程为instance初始化后,可能这个初始化对另一线程还不可见,再次初始化,这样就初始化了多次,破坏单例

解决方法:将instance变量设成violate类型,禁止重排序,保证变量对所有线程的可见性

3.饿汉式

public class Singleton {
    private Singleton() {}
    private static Singleton instance = new Singleton();
    public static Singleton getInstance() {
        return instance;
    }
}

在类初始化时产生唯一实例

优:简单

缺:在类初始化时就产生了唯一实例,过早地浪费资源

4.静态内部类

public class Singleton {
    private Singleton() {}
    public static class SingletonInit {
        public static final Singleton instance = new Singleton();
    }
    public static Singleton getInstance() {
        return SingletonInit.instance;
    }
}

优:实现了延迟加载

缺:多产生的了一个静态类

5.枚举

枚举类型是单例,直接调用Sinleton.INSTANCE即可得到实例

public enum Singleton {
    INSTANCE;
}

优:简单,安全性高,能保证单例

缺:在类初始化时就产生了唯一实例,过早地浪费资源

三.应对各种破坏单例的情况

1.反射调用私有构函数

反射可以绕过私有检查机制调用私有构造函数,破坏单例,所以可以在构造函数中加判断

private Singleton() {
    if (instance != null) {
        throw new RuntimeException("不能再次实例化");
    }
}

2.抵御clone攻击

clone会产生多个不同实例,破坏单例,只要类实现Cloneable接口,再覆盖clone方法便可避免这种情况

@Override
public Object clone() throws CloneNotSupportedException {
    return getInstance();
}

3.反序列化

给类实现Serializable接口,便可序列化。但反序列化会破坏单例模式,演示代码如下

String filePath = "xxx";
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(filePath));
Singleton originSingleton = Singleton.getInstance();
objectOutputStream.writeObject(originSingleton);

File file = new File(filePath);
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));
Singleton singleton = (Singleton) objectInputStream.readObject();
System.out.println(singleton == originSingleton);

结果为false,两个实例不一样

解决方法:在单例类中添加readResolve()方法

private Object readResolve() {
    return getInstance();
}

再用上面的代码反序列化,得到的实例将是一样的。具体为什么可以研究objectInputStream.readObject()方法实现,在类有readResolve方法时,反序列化将是调用readResolve()得到实例对象

上述为了保证单例,要写好多东西,太麻烦了。直接使用枚举实现单例,便可免去这些麻烦,为保证单例枚举类自己都做好了

参考:https://zhuanlan.zhihu.com/p/28491630

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值