单例模式以及通过反射和序列化破解单例模式

单例模式可以有以下几种方式:

  1. 饿汉方式
  2. 懒汉方式
  3. 双重检查加锁懒汉方式
  4. 内部类方式
  5. 枚举方式

破解单例模式有两种方式:通过反射的方式和通过序列化的方式。下面将一一对此进行分析。

饿汉方式非常简单,即使用一个初始化的静态变量,代码如下:

public class EagerSingleton {
   private static final EagerSingleton instance = new EagerSingleton();

   private EagerSingleton(){}

   public static EagerSingleton getInstance() {
      return instance;
   }
}
懒汉模式和饿汉模式类似,只是静态变量定义时不进行初始化,调用getInstance()时才进行初始化,这就需要考虑多线程时问题,使用synchronized关键字修饰方法即可:
class LazySingleton {
    private static LazySingleton instance = null;
    private LazySingleton() {}
    public static synchronized LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}
懒汉模式实际是一种懒加载,但是为了避免多线程时单例失效,必须对getInstance()方法进行同步。可以使用双重检查方式来避免对方法全部进行加锁:
class DoubleCheckSingleton {
    private static volatile DoubleCheckSingleton instance = null;
    private DoubleCheckSingleton() {}
    public static DoubleCheckSingleton getInstance() {
        if (instance == null) {
            synchronized (DoubleCheckSingleton.class) {
                if (instance == null) {
                    instance = new DoubleCheckSingleton();
                }
            }
        }
        return instance;
    }
}
这个需要几个注意点:静态类变量必须声明为private static volatile,这样可以 写入操作 happens-before 于每一个后续的同一个字段的读操作。在getInstance()方法中,第一次判断null后,使用同步防止多线程时破坏单例。


第四种是使用内部类的方式,也是一种懒加载的方式实现:

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

内部类只在第一次调用的时候才会被类加载器加载,实现了懒加载

同时由于instance是static final类型,保证了内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程安全性

但是上面四种方式都可以通过反射的方式来破坏单例:

InnerClassLazySingleton eagerSingleton = InnerClassLazySingleton.getInstance();
Constructor<InnerClassLazySingleton> constructor = InnerClassLazySingleton.class.getDeclaredConstructor();
constructor.setAccessible(true);

InnerClassLazySingleton eagerSingletonFromRef = constructor.newInstance();
System.out.println("使用反射破解饥渴单例模式:" + (eagerSingleton == eagerSingletonFromRef ? "否" : "是"));
并且如果实现了Serializable接口的话,通过序列化的方式也可以破坏单例:
ByteArrayOutputStream baos  = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(eagerSingleton);

ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
InnerClassLazySingleton eagerSingletonFromSerial =  (InnerClassLazySingleton) ois.readObject();
System.out.println("使用序列化破解饥渴单例模式:" + (eagerSingleton == eagerSingletonFromSerial ? "否" : "是"));
可以通过在单例类中添加readResolve()方法的方式来解决:
class EagerSingleton implements Serializable {
    private static final EagerSingleton instance = new EagerSingleton();
    private EagerSingleton() {}
    public static EagerSingleton getInstance() {
        return instance;
    }

    private Object readResolve() {
        System.out.println("print from readResolve() method");
        return getInstance();
    }

    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        System.out.println("print from readObject() method");
        ois.defaultReadObject();
    }
}
readResolve()方法是用来替换从流中读取的对象的,它在readObject(ObjectInputStream)方法之后被调用,readObject()方法即从流中读取对象的方法。这样就可以避免使用序列化方式破坏单例。


但是上面的四种方式还是无法避免反射的方式来破坏单例的情况,可以使用枚举的方式实现单例:

enum EnumSingleton {
    INSTANCE;
    
    public static void method() {
        
    }
}
通过反射方式创建实例时会抛出 Exception in thread "main" java.lang.NoSuchMethodException: net.local.singleton.EnumSingleton.<init>() 异常。并且使用枚举类Enum实现了序列化接口,并且也实现了readResolve()方法,因为使用序列化方式也没法破坏,是最理想的单例模式实现。 

转载于:https://my.oschina.net/TQNWvb/blog/160717

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值