单例模式
单例模式的实现方案有两种:饿汉式、懒汉式。
网上很多写法都存在一些问题。
饿汉式
饥饿法则,优先创建实例,需要时直接返回。
优点是代码编写简单,缺点是没有达到懒加载的效果,浪费资源。
public class Single {
private final static Single SINGLE = new Single();
private Single(){}
public static Single getInstance(){
return SINGLE;
}
}
懒汉式
顾名思义,默认不实例化,需要时再实例化。
public class Single {
private static Single single;
private Single(){}
public static Single getInstance(){
if (single == null) {
single = new Single();
}
return single;
}
}
这是线程不安全的,并发的情况下,还是会创建多个实例。
双重加锁检查
通过加锁的方式进行双重检查,这样就是线程安全的,即使并发也只会创建一个实例。
public class Single {
private static Single single;
private Single(){}
public static Single getInstance(){
if (single == null) {
synchronized (Single.class) {
if (single == null) {
single = new Single();
}
}
}
return single;
}
}
反射攻击
实际上,上面两种方式并没有达到真正的“单例”。
因为都忽略了一个很严重的问题:反射可以调用私有构造器。
例如,下面这段代码可以任意实例化单例类:
public class Client {
public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
//获取Single的构造器
Constructor<Single> constructor = Single.class.getDeclaredConstructor();
//取消访问检查
constructor.setAccessible(true);
Single s1 = constructor.newInstance();
Single s2 = constructor.newInstance();
System.out.println(s1);
System.out.println(s2);
}
}
//输出:
unit1.item3.Single@511d50c0
unit1.item3.Single@60e53b93
改造单例
弄清楚原因之后,进行改造就很简单了,只需要在私有构造器内稍作判断即可。
public class Single {
private static Single single;
private Single(){
synchronized (Single.class){
if (single != null) {
throw new RuntimeException("拒绝再次实例.");
}
single = this;
}
}
public static Single getInstance(){
if (single == null) {
synchronized (Single.class) {
if (single == null) {
single = new Single();
}
}
}
return single;
}
}
序列化对单例的破坏
当单例类允许被序列化后,如果不做处理,单例模式也会被破坏。
public class Client {
public static void main(String[] args) throws Exception {
//获取实例
Single s1 = Single.getInstance();
//序列化
File file = new File("/Users/panchanghe/temp/Single.obj");
OutputStream outputStream = new FileOutputStream(file);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(s1);
objectOutputStream.flush();
objectOutputStream.close();
outputStream.close();
//反序列化
InputStream inputStream = new FileInputStream(file);
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
Single s2 = (Single) objectInputStream.readObject();
objectInputStream.close();
inputStream.close();
System.out.println(s1);
System.out.println(s2);
}
}
输出如下:
unit1.a.Single@6f94fa3e
unit1.a.Single@4e50df2e
反序列化后得到的是一个新的实例。
任何一个readObject方法,不管是显式的还是默认的,它都会返回一个新建的实例,这个新建的实例不同于该类初始化时创建的实例。
要想解决也很简单,编写readResolve()方法,如下:
//防止序列化对单例的破坏
private Object readResolve() {
return single;
}
枚举实现单例
单元素的枚举类型已经成为实现Singleton的最佳方法。——《Effective Java》
枚举实现单例非常简单,即可以防止反射攻击,还可以防止序列化破坏,因为Enum重写了readObject方法,
public enum EnumSingle {
INSTANCE;
public static EnumSingle getInstance(){
return INSTANCE;
}
//属性、方法照常写...
public void func(){
System.out.println("func....");
}
}
反射攻击验证
Constructor<EnumSingle> constructor = EnumSingle.class.getDeclaredConstructor();
如上方法去获取构造器会报错:NoSuchMethodException
。
一旦将类声明为枚举类型,该类就自动继承了Enum,查看Enum的源码,会发现其只有一个构造器:
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
通过获取父类的构造器来实例化,测试代码修改如下:
Constructor<EnumSingle> constructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
constructor.setAccessible(true);
EnumSingle enumSingle = constructor.newInstance("instance",0);
输出如下:
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at unit1.item3.Client.main(Client.java:18)
反射时抛出异常,不能反射创建枚举对象。
查看newInstance()源码,发现如下:
Java反射时会进行检查,如果是枚举类型,会抛出异常,不允许反射实例化。
序列化破坏验证
public class Client {
public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, IOException, ClassNotFoundException {
//获取实例
EnumSingle s1 = EnumSingle.getInstance();
//序列化
File file = new File("/Users/panchanghe/temp/Single.obj");
OutputStream outputStream = new FileOutputStream(file);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(s1);
objectOutputStream.flush();
objectOutputStream.close();
outputStream.close();
//反序列化
InputStream inputStream = new FileInputStream(file);
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
EnumSingle s2 = (EnumSingle) objectInputStream.readObject();
objectInputStream.close();
inputStream.close();
System.out.println(s1);
System.out.println(s2);
System.out.println(s1 == s2);
}
}
输出如下:
INSTANCE
INSTANCE
true
使用枚举来实现,即使是序列化也不会破坏其单例模式。