單例模式--反射--防止序列化破壞單例模式

本文牽涉到的概念:
1.單例模式------惟一最佳實現方式,使用枚舉類實現
2.單例模式的幾種實現,各自的缺點
3.反射;反射是如何破壞單例模式
4.序列化;序列化如何破壞單例模式
 
單例模式
單例模式,是指在任什麼時候候,該類只能被實例化一次,在任什麼時候候,訪問該類的對象,對象都是同一的,只有一個。
 
單例模式的實現方式:
a .使用類公有的靜態成員來保存該惟一對象
public class EagerSingleton {    
        // jvm保證在任何線程訪問uniqueInstance靜態變量以前必定先建立了此實例    
        public static EagerSingleton uniqueInstance = new EagerSingleton();    
    
        // 私有的默認構造子,保證外界沒法直接實例化    
        private EagerSingleton() {    
        }    
}  

 

  
b.使用公有的靜態成員工廠方法
 
public class EagerSingleton {    
        // jvm保證在任何線程訪問uniqueInstance靜態變量以前必定先建立了此實例    
        private static EagerSingleton uniqueInstance = new EagerSingleton();    
    
        // 私有的默認構造子,保證外界沒法直接實例化    
        private EagerSingleton() {    
        }    
    
        // 提供全局訪問點獲取惟一的實例    
        public static EagerSingleton getInstance() {    
                return uniqueInstance;    
        }    
}  
//懶漢式
同步一個方法可能形成程序執行效率降低100倍,徹底沒有必要每次調用getInstance都加鎖,事實上咱們只想保證一次初始化成功,其他的快速返回而已,若是在getInstance頻繁使用的地方就要考慮從新優化了.
public class LazySingleton {    
        private static LazySingleton uniqueInstance;    
    
        private LazySingleton() {    
        }    
    
        public static synchronized LazySingleton getInstance() {    
                if (uniqueInstance == null)    
                        uniqueInstance = new LazySingleton();    
                return uniqueInstance;    
        }    
}   

 

3)"雙檢鎖"(Double-Checked Lock)儘可能將"加鎖"推遲,只在須要時"加鎖"(僅適用於Java 5.0 以上版本,volatile保證原子操做) 
happens-before:"什麼什麼必定在什麼什麼以前運行",也就是保證順序性.
如今的CPU有亂序執行的能力(也就是指令會亂序或並行運行,能夠不按咱們寫代碼的順序執行內存的存取過程),而且多個CPU之間的緩存也不保證明時同步,只有上面的happens-before所規定的狀況下才保證順序性.java

JVM可以根據CPU的特性(CPU的多級緩存系統、多核處理器等)適當的從新排序機器指令,使機器指令更符合CPU的執行特色,最大限度的發揮機器的性能.緩存

若是沒有volatile修飾符則可能出現一個線程t1的B操做和另外一線程t2的C操做之間對instance的讀寫沒有happens-before,可能會形成的現象是t1的B操做尚未徹底構形成功,但t2的C已經看到instance爲非空,這樣t2就直接返回了未徹底構造的instance的引用,t2想對instance進行操做就會出問題.app

    volatile 的功能:
1. 避免編譯器將變量緩存在寄存器裏  
2. 避免編譯器調整代碼執行的順序jvm

優化器在用到這個變量時必須每次都當心地從新讀取這個變量的值,而不是使用保存在寄存器裏的備份。性能

public class DoubleCheckedLockingSingleton {    
        // java中使用雙重檢查鎖定機制,因爲Java編譯器和JIT的優化的緣由系統沒法保證咱們指望的執行次序。    
        // 在java5.0修改了內存模型,使用volatile聲明的變量能夠強制屏蔽編譯器和JIT的優化工做    
        private volatile static DoubleCheckedLockingSingleton uniqueInstance;    
    
        private DoubleCheckedLockingSingleton() {    
        }    
    
        public static DoubleCheckedLockingSingleton getInstance() {    
                if (uniqueInstance == null) {    
                        synchronized (DoubleCheckedLockingSingleton.class) {    
                                if (uniqueInstance == null) {    
                                        uniqueInstance = new DoubleCheckedLockingSingleton();    
                                }    
                        }    
                }    
                return uniqueInstance;    
        }    
}    

 

 
4)Lazy initialization holder class 知足全部 Double-Checked Locking 知足的條件,而且沒有顯示的同步操做
public class LazyInitHolderSingleton {    
        private LazyInitHolderSingleton() {    
        }    
    
        private static class SingletonHolder {    
                private static final LazyInitHolderSingleton INSTANCE = new LazyInitHolderSingleton();    
        }    
    
        public static LazyInitHolderSingleton getInstance() {    
                return SingletonHolder.INSTANCE;    
        }    
}    

 

根據jvm規範,當某對象第一次調用LazyInitHolderSingleton.getInstance()時,LazyInitHolderSingleton類被首次主動使用,jvm對其進行初始化(此時並不會調用LazyInitHolderSingleton()構造方法;進行LazyInitHolderSingleton的類加載,初始化靜態變量),而後LazyInitHolderSingleton調用getInstance()方法,該方法中,又首次主動使用了SingletonHolder類,因此要對SingletonHolder類進行初始化(類的靜態變量首先加載,進行初始化),初始化中,INSTANCE常量被賦值時才調用了 LazyInitHolderSingleton的構造方法LazyInitHolderSingleton(),完成了實例化並返回該實例。
當再有對象(也許是在別的線程中)再次調用LazyInitHolderSingleton.getInstance()時,由於已經初始化過了,不會再進行初始化步驟,因此直接返回INSTANCE常量即同一個LazyInitHolderSingleton實例。
 
 
 
C 使用枚舉類的方式來實現單例
推薦作法
 
 
public enum SingletonClass {
INSTANCE;
 
private String name;
public void test() {
System.out.println("The Test!");
}
 
public void setName(String name){
 
this.name= name;
}
 
public String getName(){
 
return name;
}
}
 
public class TestMain {
 
public static void main(String[] args) {
 
SingletonClass one = SingletonClass.INSTANCE;
SingletonClass two = SingletonClass.INSTANCE;
 
 
one.test();
one.setName("I am a SingletonClass Instance");
System.out.println(one.getName());
 
if (one == two) {
 
System.out.println("There are same");
}
}
}

 

反射
反射是如何破壞單例模式的,單例模式的目標是,任什麼時候候該類都只有惟一的一個對象
 
好比,實現一個單例:
package com.effective.singleton;  
  
public class Elvis  
{  
    private static boolean flag = false;  
  
    private Elvis(){  
    }  
  
    private  static class SingletonHolder{  
        private static final Elvis INSTANCE = new Elvis();  
    }  
  
    public static Elvis getInstance()  
    {  
        return SingletonHolder.INSTANCE;  
    }  
  
    public void doSomethingElse()  
    {  
  
    }  
}  
使用反射的方式來實例化該類
 
package com.effective.singleton;  
  
import java.lang.reflect.Constructor;  
import java.lang.reflect.InvocationTargetException;  
  
public class ElvisReflectAttack  
{  
  
    public static void main(String[] args) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException  
    {  
        Class<?> classType = Elvis.class;  
  
        Constructor<?> c = classType.getDeclaredConstructor(null);  
        c.setAccessible(true);  
        Elvis e1 = (Elvis)c.newInstance();  
        Elvis e2 = Elvis.getInstance();  
        System.out.println(e1==e2);  
    }  
  
}  

 

輸出結果爲false,說明e1和e2不是同一個對象,到這裏,單例模式不起做用。若是e1和e2都是指向同一個對象的,那麼它們的引用值相等。
 
序列化
使用序列化的方式,單例模式是如何失效的
package com.serialize;  
  
import java.io.Serializable;  
  
public class SerSingleton implements Serializable  
{  
    private static final long serialVersionUID = 1L;  
  
    String name;  
  
    private SerSingleton()  
    {  
        System.out.println("Singleton is create");  
        name="SerSingleton";  
    }  
  
    private static SerSingleton instance = new SerSingleton();  
  
    public static SerSingleton getInstance()  
    {  
        return instance;  
    }  
  
    public static void createString()  
    {  
        System.out.println("createString in Singleton");  
    }  
}  
  
    @Test  
    public void test() throws IOException, ClassNotFoundException  
    {  
        SerSingleton s1= null;  
        SerSingleton s = SerSingleton.getInstance();  
  
        FileOutputStream fos = new FileOutputStream("SerSingleton.obj");  
        ObjectOutputStream oos = new ObjectOutputStream(fos);  
        oos.writeObject(s);  
        oos.flush();  
        oos.close();  
  
        FileInputStream fis = new FileInputStream("SerSingleton.obj");  
        ObjectInputStream ois = new ObjectInputStream(fis);  
        s1 = (SerSingleton)ois.readObject();  
      System.out.println(s==s1);  
    }  
 
輸出結果爲false。s和s1指向的對象不是同一個。
 
 
如何避免單例模式被破壞
 
1.反射
第二次實例化的時候,拋出異常
 
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
 
public class Elvis {
private static boolean flag = false;
 
private Elvis() {
synchronized (Elvis.class) {
System.out.println(" try to instance");
if (flag == false) {
System.out.println("first time instance");
flag = !flag;
} else {
throw new RuntimeException("單例模式被侵犯!");
}
}
}
 
private static class SingletonHolder {
// jvm保證在任何線程訪問INSTANCE靜態變量以前必定先建立了此實例
private static final Elvis INSTANCE = new Elvis();
}
 
public static Elvis getInstance() {
System.out.println("in getInstance");
return SingletonHolder.INSTANCE;
}
 
public void doSomethingElse() {
 
}
 
public static void main(String[] args) throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
 
Class<?> classType = Elvis.class;
Constructor<?> c = classType.getDeclaredConstructor(null);
c.setAccessible(true);
Elvis e1 = (Elvis) c.newInstance();
 
Elvis e2 = Elvis.getInstance();
 
System.out.println(e1 == e2);
}
}

 

 
輸出結果
  try to instance
first time instance
in getInstance
 try to instance
Exception in thread "main" java.lang.ExceptionInInitializerError
at chapterOne.Elvis.getInstance(Elvis.java:28)
at chapterOne.Elvis.main(Elvis.java:43)
Caused by: java.lang.RuntimeException: 單例模式被侵犯!
at chapterOne.Elvis.<init>(Elvis.java:16)
at chapterOne.Elvis.<init>(Elvis.java:9)
at chapterOne.Elvis$SingletonHolder.<clinit>(Elvis.java:23)
... 2 more
 
分析:
a.由於,反射執行了,會建立一個對象。這時候,是不走靜態方法getInstance的
b.而後,咱們嘗試去得到一個單例,會失敗。由於,咱們調用靜態方法getInstance,會嘗試建立一個實例。而此時,實例已經建立過了。
這樣,就能夠保證只有一個實例。 是達到效果了,可是,在這種狀況下,反射先於靜態方法getInstance執行。這致使,咱們沒法得到已經該實例。
因此,其實這種方法,是很差的。 除非,你能夠保證,你的getInstance方法,必定先於反射代碼執行。不然雖然有效果,可是你得不到指向該實例的引用
 
  
2.序列化
在被序列化的類中添加readResolve方法
    Deserializing an object via readUnshared invalidates the stream handle associated with the returned object. Note that this in itself does not always guarantee that the reference returned by readUnshared is unique; the deserialized object may define a readResolve method which returns an object visible to other parties, or readUnshared may return a Class object or enum constant obtainable elsewhere in the stream or through external means. If the deserialized object defines a readResolve method and the invocation of that method returns an array, then readUnshared returns a shallow clone of that array; this guarantees that the returned
array object is unique and cannot be obtained a second time from an invocation of readObject or readUnshared on the ObjectInputStream, even if the underlying data stream has been manipulated.
 
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
 
public class SerSingleton implements Serializable {
 
private static final long serialVersionUID = 1L;
String name;
 
private SerSingleton() {
System.out.println("Singleton is create");
name = "SerSingleton";
}
 
private static SerSingleton instance = new SerSingleton();
 
public static SerSingleton getInstance() {
return instance;
}
 
public static void createString() {
System.out.println("createString in Singleton");
}
private Object readResolve(){
return instance;
}
 
 
public static void main(String[] args) throws IOException, ClassNotFoundException {
SerSingleton s1 = null;
SerSingleton s = SerSingleton.getInstance();
 
FileOutputStream fos = null;
ObjectOutputStream oos = null;
 
FileInputStream fis = null;
ObjectInputStream ois = null;
try {
fos = new FileOutputStream("SerSingleton.obj");
oos = new ObjectOutputStream(fos);
oos.writeObject(s);
} finally {
oos.flush();
oos.close();
fos.close();
}
 
try{
fis = new FileInputStream("SerSingleton.obj");
ois = new ObjectInputStream(fis);
s1 = (SerSingleton) ois.readObject();
}finally{
ois.close();
fis.close();
}
System.out.println(s == s1);
}
 
}
 

 

/////////////////////////////////////////////
 
總結,實現單例模式的惟一推薦方法,使用枚舉類來實現。使用枚舉類實現單例模式,在對枚舉類進行序列化時,還不須要添加readRsolve方法就能夠避免單例模式被破壞。
使用枚舉類來實現
 
public enum SingletonClass implements Serializable {
 
INSTANCE;
private static final long serialVersionUID = 1L;
 
private String name;
 
public void test() {
System.out.println("The Test!");
}
 
public void setName(String name) {
 
this.name = name;
}
 
public String getName() {
 
return name;
}
 
public static void main(String[] args) throws IOException, ClassNotFoundException {
SingletonClass s1 = null;
SingletonClass s = SingletonClass.INSTANCE;
 
FileOutputStream fos = null;
ObjectOutputStream oos = null;
 
FileInputStream fis = null;
ObjectInputStream ois = null;
try {
fos = new FileOutputStream("SingletonClass.obj");
oos = new ObjectOutputStream(fos);
oos.writeObject(s);
} finally {
oos.flush();
oos.close();
fos.close();
}
 
try {
fis = new FileInputStream("SingletonClass.obj");
ois = new ObjectInputStream(fis);
s1 = (SingletonClass) ois.readObject();
} finally {
ois.close();
fis.close();
}
System.out.println(s == s1);
}
}
 

 

 
輸出:
true
 
 
 
引用:
 
 
 
相關文章
相關標籤/搜索