代码演示
// 懒汉式: 用的时候创建
public class LazyMan {
private LazyMan() {
synchronized (LazyMan.class) {
}
}
private volatile static LazyMan lazyMan;
// 双重检验锁 DCL懒汉
public static LazyMan getLazyMan() {
if (lazyMan == null) {
synchronized (LazyMan.class) {
if (lazyMan == null) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
/**
* 反射破环单例模式
* @param args
*/
public static void main(String[] args) throws NoSuchMethodException,
IllegalAccessException, InvocationTargetException, InstantiationException {
// 正常获取实例
LazyMan lazyMan1 = LazyMan.getLazyMan();
// 使用反射创建LazyMan实例
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
// 通过反射获取的实例
LazyMan lazyMan2 = declaredConstructor.newInstance();
System.out.println(lazyMan1 == lazyMan2);
}
}
- 控制台输出结果false, 表示两个实例不是同一个对象
false
如何避免
- (1)修改构造方法(不能完全避免)
private LazyMan() {
synchronized (LazyMan.class) {
if (lazyMan != null) {
throw new RuntimeException("不要试图破坏单例模式");
}
}
}
这种避免的前提式final的lazyMan已经被new出来的, 如果使用反射的方式生成两个实例, 依然不满足单例,代码如下
/**
* 反射破环单例模式
* @param args
*/
public static void main(String[] args) throws NoSuchMethodException,
IllegalAccessException, InvocationTargetException, InstantiationException {
// 使用反射创建LazyMan实例
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
//通过反射创建两个实例对象
LazyMan lazyMan2 = declaredConstructor.newInstance();
LazyMan lazyMan3 = declaredConstructor.newInstance();
System.out.println(lazyMan3 == lazyMan2);
}
- 控制台输出结果false, 表示两个实例不是同一个对象
false
- (2)继续修改构造方法(还是不能完全解决)
// 添加一个标志位, 当第一个实例生成后, flag变为true, 以后构造方法不能在生成实例
private static boolean flag = false;
private LazyMan() {
synchronized (LazyMan.class) {
if (flag == false){
flag = true;
} else{
throw new RuntimeException("不要试图破坏单例模式");
}
}
}
- 按照上面的方式修改了构造器后, 还是可以通过反射破解, 代码如下
/**
* 反射破环单例模式
* @param args
*/
public static void main(String[] args) throws NoSuchMethodException,
IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
// 使用反射创建LazyMan实例
// 通过反射获取flag,并破坏私有权限
Field flag = LazyMan.class.getDeclaredField("flag");
flag.setAccessible(true);
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan lazyMan2 = declaredConstructor.newInstance();
// 在创建lazyMan2实例的时候将flag的值改为false
flag.set(lazyMan2, false);
LazyMan lazyMan3 = declaredConstructor.newInstance();
System.out.println(lazyMan3 == lazyMan2);
}
- 控制台输出结果false, 表示两个实例不是同一个对象
false
到底如何彻底解决呢
反射的newInstance()源码分析, 如果是枚举类则不能通过反射创建实例
@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;
}
- 通过枚举来生成单例
// 枚举本身是什么, 也是一个CLASS类
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return EnumSingle.INSTANCE;
}
}
class Test{
public static void main(String[] args) {
EnumSingle instance1 = EnumSingle.INSTANCE;
EnumSingle instance2 = EnumSingle.INSTANCE;
System.out.println(instance1 == instance2);
}
}