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


本文牽涉到的概念:
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所規定的情況下才保證順序性.

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

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

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

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

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
 
 
 
引用:
 
 
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM