今天有同學問我箭跳,餓漢單例模式為什么一定要加final關(guān)鍵字?即便使用多個線程去訪問潭千,加了final關(guān)鍵詞和不加效果都是一樣的呀谱姓。那么可不可以不加final,只用static呢刨晴?(如下寫法)
public class Singleton {
private static Singleton singleton = new Singleton();
private Singleton() {
}
public static Singleton getSingle() {
System.out.println(singleton);
return singleton;
}
}
答案是 不可以屉来!必須加上final關(guān)鍵詞!
由于在網(wǎng)上查詢狈癞,大多數(shù)博客對這個問題沒有明確說明茄靠,有的甚至說的還是錯的,所以特來實證這個問題蝶桶。
首先你要知道的是慨绳,反射可以隨時隨地脫下JVM的底褲。真竖。儡蔓。所以Java中的任何權(quán)限控制,在反射環(huán)境下疼邀,基本是不存在的喂江。
大概描述下思路:
1.我先用反射調(diào)用單例類的構(gòu)造函數(shù),創(chuàng)建出新的單例對象來旁振。
2.使用field去訪問到原本的單例對象获询。
3.使用set方法把新創(chuàng)建的單例對象賦值給原本的對象。
4.查看是否能賦值成功拐袜。
基于不加final的后果如下程序可證:
public class Main {
public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
System.out.println("第一次拿到單例模式創(chuàng)建的對象: " + Singleton.getSingle());
Class<Singleton> clazz = Singleton.class;
Constructor<Singleton> c0 = clazz.getDeclaredConstructor();
c0.setAccessible(true);
Singleton po = c0.newInstance();
System.out.println("反射創(chuàng)建出來的對象: " + po);
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
Singleton singleton1 = (Singleton) field.get(Singleton.getSingle());
System.out.println("拿到單例模式創(chuàng)建的對象: " + singleton1);
field.set(Singleton.getSingle(), po); //把反射創(chuàng)建出來的對象賦值給單例對象
System.out.println("第二次拿到單例模式創(chuàng)建的對象: " + Singleton.getSingle());
}
}
}
----------------------------------------------------
運行結(jié)果:
第一次拿到單例模式創(chuàng)建的對象: com.service.Singleton@16b98e56
反射創(chuàng)建出來的對象: com.service.Singleton@7ef20235
拿到單例模式創(chuàng)建的對象: com.service.Singleton@16b98e56
第二次拿到單例模式創(chuàng)建的對象: com.service.Singleton@7ef20235
發(fā)現(xiàn)了吧吉嚣,它的地址變了,不該改變的實例蹬铺,改變了尝哆!
如果加上final
運行結(jié)果為:
第一次拿到單例模式創(chuàng)建的對象: com.service.Singleton@16b98e56
反射創(chuàng)建出來的對象: com.service.Singleton@7ef20235
拿到單例模式創(chuàng)建的對象: 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是更安全的單例方式甜攀。
除此之外秋泄,網(wǎng)上還有一種說法琐馆,為了保證只能創(chuàng)建一個實例,杜絕反射通過構(gòu)造函數(shù)作惡恒序,可以使用以下方式:
private static volatile boolean flag = false;
private Singleton(){
synchronized (Singleton.class) {
if(!flag){
flag = true;
}else{
throw new RuntimeException("單例只能創(chuàng)建一個");
}
}
}