实现1
// 问题1:为什么加 final
// 问题2:如果实现了序列化接口, 还要做什么来防止反序列化破坏单例
public final class Singleton implements Serializable {
// 问题3:为什么设置为私有? 是否能防止反射创建新的实例?
private Singleton() {
}
// 问题4:这样初始化是否能保证单例对象创建时的线程安全?
private static final Singleton INSTANCE = new Singleton();
// 问题5:为什么提供静态方法而不是直接将 INSTANCE 设置为 public, 说出你知道的理由
public static Singleton getInstance() {
return INSTANCE;
}
public Object readResolve() {
return INSTANCE;
}
}
问题一:
因为防止子类重写方法,而破坏单例
问题二:
反序列化会创建一个新的对象,从而破坏单例。这就需要写一个readResolve方法,这个方法会再JVM反序列化的时候调用。所以即使反序列化,主要有这个方法,依然会返回当前这个单例对象
问题三:
1.私有构造方法是为了防止通过构造器破坏单例
2.私有构造器不能防止反射破坏单例(反射怎么破坏?学完回来补充)
问题四:
没用线程安全问题。因为加了static,就会在类加载的时候创建,JVM会保证线程安全
问题五:
1.面向对象封装的思想
2.可以方便后期修改(比如把现在饿汉式的变成懒汉式的)
实现2
问题一:
枚举类的枚举,再反编译后相当于是个静态的成员变量,所以INSTANCE对象相当于是个静态成员变量,当然就是单例的。
问题二:
应为INSTANCE是静态的,所以也是再类加载的时候由JVM保证线程安全
问题三:
不会被反射破坏。(原因看下面)
问题四:
不会被反序列化破坏(原因看下面)
问题五:
因为枚举对象时静态成员变量,所以时饿汉式的
问题六:
将初始化逻辑写在枚举类的构造方法中
为什么枚举的单例不会被反射破坏
public enum EnumSingleton {
INSTANCE;
public EnumSingleton getInstance(){
return INSTANCE;
}
public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
EnumSingleton singleton1=EnumSingleton.INSTANCE;
EnumSingleton singleton2=EnumSingleton.INSTANCE;
System.out.println("正常情况下,实例化两个实例是否相同:"+(singleton1==singleton2));
Constructor<EnumSingleton> constructor= null;
constructor = EnumSingleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
EnumSingleton singleton3= null;
singleton3 = constructor.newInstance();
System.out.println(singleton1+"\n"+singleton2+"\n"+singleton3);
System.out.println("通过反射攻击单例模式情况下,实例化两个实例是否相同:"+(singleton1==singleton3));
}
}
以上使用反射攻击单例的代码种会在constructor = EnumSingleton.class.getDeclaredConstructor();
,报 1 Exception in thread "main" java.lang.NoSuchMethodException: com.lxp.pattern.singleton.EnumSingleton.<init>()
的错误,这就说明,枚举类因为没用无参构造方法,所以不能被攻击。
但是,枚举类可以获取父类的参数为(String.class,int.class)
的构造器,那用这个构造器能否攻击?
public enum EnumSingleton {
INSTANCE;
public EnumSingleton getInstance(){
return INSTANCE;
}
public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
EnumSingleton singleton1=EnumSingleton.INSTANCE;
EnumSingleton singleton2=EnumSingleton.INSTANCE;
System.out.println("正常情况下,实例化两个实例是否相同:"+(singleton1==singleton2));
Constructor<EnumSingleton> constructor= null;
// constructor = EnumSingleton.class.getDeclaredConstructor();
constructor = EnumSingleton.class.getDeclaredConstructor(String.class,int.class);//其父类的构造器
constructor.setAccessible(true);
EnumSingleton singleton3= null;
//singleton3 = constructor.newInstance();
singleton3 = constructor.newInstance("testInstance",66);
System.out.println(singleton1+"\n"+singleton2+"\n"+singleton3);
System.out.println("通过反射攻击单例模式情况下,实例化两个实例是否相同:"+(singleton1==singleton3));
}
}
还是会在 singleton3 = constructor.newInstance("testInstance",66);
,报Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
的错误。
进入源码
@CallerSensitive
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
可以通过 if ((clazz.getModifiers() & Modifier.ENUM) != 0)
得出结论反射在通过newInstance
创建对象时,会检查该类是否ENUM修饰,如果是则抛出异常,反射失败。
所以,枚举类不能通过反射破坏单例。
为什么不会被反序列化破坏?(这个是自己想的,不一定对)
因为枚举类反序列化时通过Enum类的一个valueOf
方法得到的对象
这个方法的源码:
public static <T extends Enum<T>> T valueOf(Class<T> enumType,
String name) {
T result = enumType.enumConstantDirectory().get(name);
if (result != null)
return result;
if (name == null)
throw new NullPointerException("Name is null");
throw new IllegalArgumentException(
"No enum constant " + enumType.getCanonicalName() + "." + name);
}
枚举类会将枚举对象的名字和对象,放入一个Map中,这个方法就是从map中通过枚举对象的名字得到枚举对象,从而实现反序列化。所以得到的对象还是同一个。
实现3
这是一个懒汉式的单例创建方法,只有第一次的时候才创建
问题就是锁的范围很大,每次想要判断对象是否为空,都要先获取锁。
怎么改进
该层双重检查,并且使用volatile改进双重检查因为指令重排而造成的问题
关于双重检查,可以看线程并发的笔记
实现4
问题1:
懒汉式。
分析:这里是有一个静态内部类来实例化对象的。按理说静态的,应该是饿汉,为什么是懒汉?因为static虽然是类加载的时候才初始化,但是这是个内部的类,只有通过getInstance才可以用到这个内部类,所以也就变成了用到的时候才创建这个对象。
问题2:
因为是static,所以JVM保证并发安全
总结
书中是推荐使用枚举来实现单例(简介,高效),他是饿汉的
此处的实现4也很不错,他是懒汉的