今天有同学问我,饿汉单例模式为什么一定要加final关键字?即便使用多个线程去访问,加了final关键词和不加效果都是一样的呀。那么可不可以不加final,只用static呢?(如下写法)
public class Singleton {
private static Singleton singleton = new Singleton();
private Singleton() {
}
public static Singleton getSingle() {
System.out.println(singleton);
return singleton;
}
}
答案是 不可以!必须加上final关键词!
由于在网上查询,大多数博客对这个问题没有明确说明,有的甚至说的还是错的,所以特来实证这个问题。
首先你要知道的是,反射可以随时随地脱下JVM的底裤。。。所以Java中的任何权限控制,在反射环境下,基本是不存在的。
大概描述下思路:
1.我先用反射调用单例类的构造函数,创建出新的单例对象来。
2.使用field去访问到原本的单例对象。
3.使用set方法把新创建的单例对象赋值给原本的对象。
4.查看是否能赋值成功。
基于不加final的后果如下程序可证:
public class Main {
public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
System.out.println("第一次拿到单例模式创建的对象: " + Singleton.getSingle());
Class<Singleton> clazz = Singleton.class;
Constructor<Singleton> c0 = clazz.getDeclaredConstructor();
c0.setAccessible(true);
Singleton po = c0.newInstance();
System.out.println("反射创建出来的对象: " + po);
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
Singleton singleton1 = (Singleton) field.get(Singleton.getSingle());
System.out.println("拿到单例模式创建的对象: " + singleton1);
field.set(Singleton.getSingle(), po); //把反射创建出来的对象赋值给单例对象
System.out.println("第二次拿到单例模式创建的对象: " + Singleton.getSingle());
}
}
}
----------------------------------------------------
运行结果:
第一次拿到单例模式创建的对象: com.service.Singleton@16b98e56
反射创建出来的对象: com.service.Singleton@7ef20235
拿到单例模式创建的对象: com.service.Singleton@16b98e56
第二次拿到单例模式创建的对象: com.service.Singleton@7ef20235
发现了吧,它的地址变了,不该改变的实例,改变了!
如果加上final
运行结果为:
第一次拿到单例模式创建的对象: com.service.Singleton@16b98e56
反射创建出来的对象: com.service.Singleton@7ef20235
拿到单例模式创建的对象: com.service.Singleton@16b98e56
Exception in thread "main" java.lang.IllegalAccessException: Can not set static final com.service.Singleton field com.service.Singleton.singleton to com.realife.service.Singleton
at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:76)
at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:80)
at sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl.set(UnsafeQualifiedStaticObjectFieldAccessorImpl.java:77)
at java.lang.reflect.Field.set(Field.java:764)
at com.service.Main.main(Main.java:23)
所以这样看来,加上final是更安全的单例方式。
除此之外,网上还有一种说法,为了保证只能创建一个实例,杜绝反射通过构造函数作恶,可以使用以下方式:
private static volatile boolean flag = false;
private Singleton(){
synchronized (Singleton.class) {
if(!flag){
flag = true;
}else{
throw new RuntimeException("单例只能创建一个");
}
}
}