如何避免单例模式被破坏
Posted 我是攻城师
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何避免单例模式被破坏相关的知识,希望对你有一定的参考价值。
单例模式几乎每个开发者都会用,但想要写出比较健壮的单例程序,其实并不容易。
这里不再讨论单例的模式的n种写法,仅仅讨论如何避免单例模式被破坏,看下面的一个例子:
public class SimpleSingleton {
private final static SimpleSingleton ourInstance = new SimpleSingleton();
private SimpleSingleton() {
}
public static SimpleSingleton getInstance() {
return ourInstance;
}
}
这是一个最简单的饿汉式的单例实现,在类进行初始化的时候会安全的创建实例,从而不需要同步。但这么实现,真的能保证任何时候只有一个实例吗?
答案是否定的。
在Java里面,创建对象有4种方式:
(1)new
(2)反射
(3)克隆
(4)反序列化
上面实现的单例,我们通过new确实能保证单例,但是后面的几种方式,都会破坏单例模式。
先说反射的方式,反射在带来的灵活性的同时也破坏了Java封装的特性,通过反射可以访问类里面所有的私有属性和方法。所以反射访问私有构造器是可以非常容易的创建的多个对象实例,从而破坏单例模式。
接着说克隆,这个破坏在大部分时候可以避免,因为想要克隆对象,我们必须实现Cloneable接口,然后重写clone方法,在clone的返回值处,可以返回任何实例。
最后说下序列化和反序列化,如果我们的类没有定义序列化的方法,那么在反序列化的时候,会重新生成一个新的实例,所以这也相当于破坏了单例模式。
最后还有一种不常见的破坏的场景,就是通过我们自定义类加载器来加载类,导致类本身都不是同一个类,这种场景在web项目有多级类加载器的时候比较常见,可以通过一个共用的父加载器来解决这个单例的问题,或者通过需要加载单例的类的时候,使用创建该类本身的加载器去创建,如果不在一个线程里面可以通过线程的上下文来传递类加载器。
我们改下改进后的单例模式:
public class Singleton implements Serializable,Cloneable {
//在类初始化期间,执行由JVM保证线程安全
private static Singleton singleton=new Singleton();
//避免反射和多类加载器破坏
private Singleton() {
if (Singleton.singleton != null) {
throw new InstantiationError("Creating of this object is not allowed.");
}
}
public static Singleton getInstance(){
return singleton;
}
//避免克隆破坏
@Override
protected Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException("Cloning of this class is not allowed");
// return super.clone();
}
// //避免反序列破坏
protected Object readResolve() {
return singleton;
}
}
正确的编写单例模式,其实是需要很多注意事项的,所以在jdk5之后,推荐使用枚举来创建单例类,通过枚举创建的类其实已经帮我们考虑到了上面的所有问题,不用担心其他的一些情况,JVM内部在创建的时候会自动给枚举的类做特殊处理,从而保证其在各种情况下保持唯一的实例。
以上是关于如何避免单例模式被破坏的主要内容,如果未能解决你的问题,请参考以下文章