說到單例設計模式,你們應該都比較熟悉,也能說個一二三,單例單例,無非就是 保證一個類只有一個對象實例嘛,通常就是私有化構造函數,而後再暴露一個方法提供一個實例,確實沒錯,可是怎麼樣保證一個單例的安全性呢,私有構造函數,那若是反射強勢調用呢?再好比序列化一個對象後,反序列化呢?生成的對象是否仍是同樣的?java
單例模式如今的寫法確實也是有蠻多種,總結一下,大概有以下幾種:面試
那麼每種寫法到底有什麼區別呢?哪一種纔是最適合的,話很少說,直接擼代碼~設計模式
/**
* @FileName: com.example.hik.lib
* @Desription: 描述功能
* @Anthor: taolin
* @Version V2.0 <描述當前版本功能>
*/
public class SingleInstanceDemo {
private static SingleInstanceDemo sSingleInstanceDemo;
private SingleInstanceDemo(){
}
public synchronized static SingleInstanceDemo getInstance(){
if (sSingleInstanceDemo==null)
sSingleInstanceDemo = new SingleInstanceDemo();
return sSingleInstanceDemo;
}
}
複製代碼
代碼很簡單,這種方式是線程安全的,可是很明顯,每次調用方法,都須要先得到同步鎖,性能比較低,不建議這麼寫安全
/**
* @FileName: com.example.hik.lib
* @Desription: 描述功能
* @Anthor: taolin
* @Date: 2019/2/21
* @Version V2.0 <描述當前版本功能>
*/
public class SingleInstanceDemo {
private static SingleInstanceDemo sSingleInstanceDemo = new SingleInstanceDemo();
private SingleInstanceDemo(){
}
public synchronized static SingleInstanceDemo getInstance(){
return sSingleInstanceDemo;
}
}
複製代碼
這種寫法,不能確保你的實例是在調用getInstance方法時生成的,由於類的加載機制是在可能須要使用到這個類的時候就加載(好比其餘地方引用到了這個類名等等),不清楚的能夠看下上篇文章 靜態變量的生命週期,因此這種也不能達到懶加載的效果。bash
/**
* @FileName: com.example.hik.lib
* @Desription: 描述功能
* @Anthor: taolin
* @Date: 2019/2/21
* @Version V2.0 <描述當前版本功能>
*/
public class SingleInstanceDemo {
private static SingleInstanceDemo sSingleInstanceDemo;
private SingleInstanceDemo(){
}
public static SingleInstanceDemo getInstance(){
if (sSingleInstanceDemo==null){
synchronized (SingleInstanceDemo.class){
if (sSingleInstanceDemo==null){
sSingleInstanceDemo = new SingleInstanceDemo();
}
}
}
return sSingleInstanceDemo;
}
}
複製代碼
能夠看到,把synchronized關鍵字是移到了內部,保證不用每次調用方法都得獲取同步鎖,性能有必定的提高,可是有一個問題,在Java指令中,對象的建立和賦值不是一步操做的,JVM會對代碼進行必定的指令重排序(具體規則就很少介紹了,自行google),也就是說可能JVM會先直接賦值給instance成員,而後再去初始化這個sSingleInstanceDemo實例,這樣就會出現問題函數
固然也就解決辦法,加上volatile關鍵字就行了,能夠禁止指令重排序post
/**
* @FileName: com.example.hik.lib
* @Desription: 描述功能
* @Anthor: taolin
* @Date: 2019/2/21
* @Version V2.0 <描述當前版本功能>
*/
public class SingleInstanceDemo {
public static class InnerClass{
private static final SingleInstanceDemo sSingleInstanceDemo = new SingleInstanceDemo();
}
private SingleInstanceDemo(){
}
public static SingleInstanceDemo getInstance(){
return InnerClass.sSingleInstanceDemo;
}
}
複製代碼
乍一看!咦,好像和餓漢式有點像,只不過這裏聲明瞭一個私有的靜態內部類,這樣的區別就在於:性能
靜態sSingleInstanceDemo對象的生成必定是在調用getInstance()方法的時候生成的,由於它是跟隨着InnerClass這個類的加載而產生的,它自己是一個私有類,也保證了不會有其餘的地方來調用InnerClass,這種寫法比較推薦ui
/**
* @FileName: com.example.hik.lib
* @Desription: 描述功能
* @Anthor: taolin
* @Date: 2019/2/21
* @Version V2.0 <描述當前版本功能>
*/
public enum SingleInstanceDemo {
INSTANCE;
private SingleInstanceDemo() {
}
}
複製代碼
單例的枚舉實如今《Effective Java》中有提到,由於其功能完整、使用簡潔、無償地提供了序列化機制、在面對複雜的序列化或者反射攻擊時仍然能夠絕對防止屢次實例化等優勢,單元素的枚舉類型被認爲是實現Singleton的最佳方法。google
可是枚舉類就內存消耗是比正常類要大的,因此,看狀況選擇適合本身的最好
咱們先來寫個demo來看看,是否是反射和反序列化真的會致使單例模式的問題
package com.example.hik.lib;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
public class MyClass {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//咱們經過靜態內部類方式,獲取單例對象
SingleInstanceDemo instance = SingleInstanceDemo.getInstance();
//經過反射來獲取一個對象
SingleInstanceDemo instance2 = null;
Class<SingleInstanceDemo> singleInstanceDemoClass = SingleInstanceDemo.class;
try {
Constructor<SingleInstanceDemo> constructor = singleInstanceDemoClass.getDeclaredConstructor(null);
constructor.setAccessible(true);
instance2 = constructor.newInstance();
} catch (Exception mE) {
mE.printStackTrace();
}
System.out.println("reflect obj :"+(instance==instance2));
// 1. 把對象instance寫入硬盤文件
FileOutputStream fos = new FileOutputStream("object.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(instance);
oos.close();
fos.close();
// 2. 把硬盤文件上的對象讀出來
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt"));
SingleInstanceDemo instance3 = (SingleInstanceDemo) ois.readObject();
System.out.println("Deserialize obj :"+(instance==instance3));
}
}
複製代碼
run一下上面的代碼能夠看到
reflect obj :false
Deserialize obj :false
Process finished with exit code 0
複製代碼
竟然都是false,也就是咱們經過反射和反序列生成的對象和單例對象是不同的,那麼豈不是單例就不是單例的意義了,咱們來改進一下代碼
/**
* @FileName: com.example.hik.lib
* @Desription: 描述功能
* @Anthor: taolin
* @Date: 2019/2/21
* @Version V2.0 <描述當前版本功能>
*/
public class SingleInstanceDemo implements Serializable {
public static class InnerClass{
public static final SingleInstanceDemo sSingleInstanceDemo = new SingleInstanceDemo();
}
private SingleInstanceDemo(){
if (null!=InnerClass.sSingleInstanceDemo){
throw new RuntimeException("不要用反射哦");
}
}
public static SingleInstanceDemo getInstance(){
return InnerClass.sSingleInstanceDemo;
}
private Object readResolve() throws ObjectStreamException {
return InnerClass.sSingleInstanceDemo;
}
}
複製代碼
解決辦法:
再Run一下主代碼,能夠看到
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.example.hik.lib.MyClass.main(MyClass.java:20)
Caused by: java.lang.RuntimeException: 不要用反射哦
at com.example.hik.lib.SingleInstanceDemo.<init>(SingleInstanceDemo.java:19)
... 5 more
Deserialize obj :true
Process finished with exit code 0
複製代碼
反射會拋出異常,而反序列化後對象也是和以前的單例是同樣的,這樣就大功告成了~
主要仍是但願小夥伴能真正弄清楚每一個單例模式的意義和不足之處,這樣不論是在面試仍是在平常開發中可以更好的掌握單例模式~比心❤