目录
一、饿汉单例(实现Serializable)
1、破坏单例的三种情况
(1)反射破坏单例
(2)反序列化破坏单例
(3)Unsafe破坏单例
2、饿汉单例(利用枚举实现)
二、懒汉单例(DCL实现)
1、多线程时会发生的错误
2、懒汉单例(内部类实现)
三、在哪些地方用到了单例模式
一、饿汉单例(实现Serializable)
在类执行初始化操作时,单例就会被创建,JVM会保证线程安全。
public class Singleton implements Serializable{
private Singleton() {
System.out.println("111");
}
private static final Singleton INS = new Singleton();//先创建好对象
public static Singleton getInstance() {//在需要使用时直接调用
return INS;
}
}
1、破坏单例的三种情况
(1)反射破坏单例
reflection(Singleton.class);
反射后创建了两个对象,使得单例被破坏。要想预防,在单例接口中加上以下代码
private Singleton() { if(INS!=null) { throw new RuntimeException("单例不可被创建"); } System.out.println("111"); }
直接抛出异常。
(2)反序列化破坏单例
serializable(Singleton.getInstance());
- 将单例对象转换为字节流,又将字节流读取为对象,就实现了单例破坏;
- 因为重新被读取的对象并不是原来的对象,而是新建的一个对象。
private static void serializable(Object instance) throws IOException,ClassNotFoundException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); Objectoutputstream oos = new Objectoutputstream(bos); oos.writeObject(instance); ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray())); system.out.println("反序列化创建实例:" + ois.readObject()); }
想要预防,在单例代码中实现这个方法
public Object readResolve(){ return INS; }
这样就会利用这个方法的返回值作为结果返回,就不会用字节数组反序列化生成的对象了,这样就保证了对象的单例。
(3)Unsafe破坏单例
unsafe(Singleton.class);
利用实例的类型来创建一个实例,不会走构造方法来创建。
private static void unsafe(Class<?> clazz) throws InstantiationException { Object o = Unsafeutils.getUnsafe().allocateInstance(clazz); system.out.println( "Unsafe 创建实例:" + o); }
2、饿汉单例(利用枚举实现)
public enum Singleton{
INS;
}
- 这种方法实现可以避免被,反射和反序列化破坏单例;
- 在反射时,java会做相应的检查,以阻止调用枚举类的构造。
- 在反序列化时,java会直接返回枚举类实现的对象。
- 挡不住unsafe。
二、懒汉单例(DCL实现)
public class singleton implements serializable {
private singleton() { system.out.println("private Singleton()");}
private static volatile singleton INSTANCE = null;
public static singleton getInstance() {
if (INSTANCE == null) {
synchronized (singleton.class) {
if (INSTANCE == null) {
INSTANCE = new singleton();
}
}
}
return INSTANCE;
}
- 双端检索可以避免对象被重复创建;
- 如果没有第二次检查null,就可能发生,一个等待锁的线程,进入创建对象的程序;
- 添加volatile关键字可以避免指令重排序,因为在JVM中,构建对象的指令有一定的因果关系,如果不使用volatile方法的话,就会产生构建失败的情况。
1、多线程时会发生的错误
- 在JVM执行INSTANCE=对象后,另一个线程直接返回了INSTANCE对象,但是这个对象的成员变量是没有被完全赋值的;
- 在加上volatile后,指令后会加上一个内存屏障,这样就阻止了内存重新排序。
2、懒汉单例(内部类实现)
既能保证线程安全,又可以消灭懒汉在第一次使用时才创建对象的特性。
三、在哪些地方用到了单例模式
- Runtime类
- System类
- Collection类
- Comparator接口
- Comparators类