序言
两年之前其实我们讲过单例模式,但是当时是有疏忽的,所以填一下两年之前的坑,设计模式之单例设计模式。这次我们全面的去构造一个安全的,高效率的单例。
危险且无用的行为
你一定写过这样的代码,其实我也写过。
public static SingleModel getInstance(){
if(instance == null){
// 同步锁
synchronized (SingleModel.class){
//这里需2次判空
if(instance==null){
instance = new SingleModel();
}
}
instance = new SingleModel();
}
return instance;
}
这段代码是我以前写的代码,这段代码乍一看,觉得没问题,线程安全的,且不说别的地方,我可以告诉你,这段代码并不是线程安全的,随着我不断在工作中吸取经验,我发现,这段代码应该是最无用而且危险的代码,但是很多人还在使用它,甚至引以为豪,认为自己的代码足够安全。这段代码为什么不安全,其实之后我意识到了这个问题并且出了一篇博客批评自己。为什么不安全?
而且
他也不是单例安全的,因为不能防止反编译和反射。尽管你可以把构造方法设置为private。
我来演示一个案例来证明一下:
public class SeriableSingleton implements Serializable {
public final static SeriableSingleton INSTANCE = new SeriableSingleton();
private SeriableSingleton() {
}
public static SeriableSingleton getInstance() {
return INSTANCE;
}
}
SeriableSingleton instance = SeriableSingleton.getInstance();
SeriableSingleton instance1 = SeriableSingleton.getInstance();
Class<SeriableSingleton> seriableSingletonClass = SeriableSingleton.class;
SeriableSingleton singleton = seriableSingletonClass.newInstance();
System.out.println(instance == instance1);
System.out.println(singleton == instance);
不妨執行一下看看結果,单例被破坏。所以这个并不好,也很笨,其实创建一个对象也是比较费时费力的事情,上去就创一个并不是一个明智的选择,所以有了所谓的懒汉式创建,可是懒汉式双锁是一个愚蠢的选择, 那么我们应该如何去正确的创建一个单例对象呢?
怎么做
为了保证单例模式不被破坏,必须声明所有的实例字段为transient,并且并提供一个readResolve方法,否则,每当序列化的实例被反序列化时都会被创建一个新的对象。那应该怎么办。
public class SafeSingleton implements Serializable {
private SafeSingleton() {
if (SafeSingletonHolder.instance != null) {
throw new RuntimeException("不允许创建多个实例");
}
}
private static class SafeSingletonHolder {
private static SafeSingleton instance = new SafeSingleton();
}
public static SafeSingleton getInstance() {
return SafeSingletonHolder.instance;
}
private Object readResolve(){ return SafeSingletonHolder.instance; }
}
首先这是一个懒加载的,其次这是一个无法被反射攻击的,同样的,序列化也无法攻击。所以说创建单例最好的是使用内部类创建!
结束语
之前我们写的代码是有问题的,在任何阶段,谁也无法保证写出没有任何问题的代码,但是我们要不断的去升级,去反省自己之前的工作,是否有不足,是否可以做的更好,代码是否有最优的解决方案,当前代码是否犯了同样的问题,这样,我们才能够不断的提升,不断的进步。为实现梦想添砖加瓦。