1. 程式人生 > >Java實現單例模式之餓漢式、懶漢式、列舉式,帶測試。

Java實現單例模式之餓漢式、懶漢式、列舉式,帶測試。

Java實現單例的3種普遍的模式,餓漢式、懶漢式、列舉式。

具體程式碼如下:

package com.lcx.mode;


/**
 * 
 * 餓漢式單例,不管以後用不用這個物件,我們一開始就建立這個物件的例項,
 * 需要的時候就返回已建立好的例項物件,所以比較飢餓,故此叫餓漢式單例。
 * @author 
 *
 */
public class SingletonHanger {
	private static final SingletonHanger instance = new SingletonHanger();
	private SingletonHanger() {
	}
	public static SingletonHanger getInstance(){
		return instance;
	}
}
/**
 * 懶漢漢式單例,在需要單例物件的時候,才建立唯一的單例物件,以後再次呼叫,返回的也是第一建立的單例物件
 * 將靜態成員初始化為null,在獲取單例的時候才建立,故此叫懶漢式。
 * @author 
 *
 */
class SingletonLazy{
	private static SingletonLazy instance = null;
	private SingletonLazy() {
	}
	/**
	 * 此方法實現的單例,無法在多執行緒中使用,多線可以同時進入if方法,會導致生成多個單例物件。
	 * @return
	 */
	public static SingletonLazy getInstance1(){
		if(instance==null){
			instance = new SingletonLazy();
		}
		return instance;
	}
	/**
	 * 大家都會想到同步,可以同步方法實現多執行緒的單例
	 * 但是這種方法不可取,嚴重影響效能,因為每次去取單例都要檢查方法,所以只能用同步程式碼塊的方式實現同步。
	 * @return
	 */
	public static synchronized SingletonLazy getInstance2(){
		if(instance==null){
			instance = new SingletonLazy();
		}
		return instance;
	}
	/**
	 * 用同步程式碼塊的方式,在判斷單例是否存在的if方法裡使用同步程式碼塊,在同步程式碼塊中再次檢查是否單例已經生成,
	 * 這也就是網上說的 雙重檢查加鎖的方法
	 * @return
	 */
	public static synchronized SingletonLazy getInstance3(){
		if(instance==null){
			synchronized (SingletonLazy.class) {
				if(instance==null){
					instance = new SingletonLazy();
				}
			}
		}
		return instance;
	}
}
/**
 * 
 * 使用列舉實現單例模式,也是Effective Java中推薦使用的方式
 * 根據具體情況進行例項化,對列舉不熟悉的同學,可以參考我的部落格 JAVA 列舉類的初步理解。
 * 它的好處:更加簡潔,無償提供了序列化機制,絕對防止多次例項化,即使面對複雜的序列和反射攻擊。
 * @author 
 *
 */
enum SingletionEnum{
	SingletionEnum("單例的列舉方式");
	private String str ;
	private SingletionEnum(String str){
		this.setStr(str);
	}
	public String getStr() {
		return str;
	}
	public void setStr(String str) {
		this.str = str;
	}
	
}

以上的單例模式就不測試,大家可以去測試,判斷物件的hashcode是否一致來判斷是否為同一個物件。

惡漢式、懶漢式的方式還不能防止反射來實現多個例項,通過反射的方式,設定ACcessible.setAccessible方法可以呼叫私有的構造器,可以修改構造器,讓它在被要求建立第二個例項的時候丟擲異常。

其實這樣還不能保證單例,當序列化後,反序列化是還可以建立一個新的例項,在單例類中新增readResolve()方法進行防止。

程式碼如下:

package com.lcx.mode;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * 懶漢漢式單例,在需要單例物件的時候,才建立唯一的單例物件,以後再次呼叫,返回的也是第一建立的單例物件
 * 將靜態成員初始化為null,在獲取單例的時候才建立,故此叫懶漢式。
 * @author 
 *
 */
public class Singleton implements Serializable{
	/**
	 * 
	 */
	private static final long serialVersionUID = -5271537207137321645L;
	private static Singleton instance = null;
	private static int i = 1;
	private Singleton() {
		/**
		 * 防止反射攻擊,只執行呼叫一次構造器,第二次拋異常
		 */
		if(i==1){
			i++;
		}else{
			throw new RuntimeException("只能呼叫一次建構函式");
		}
		System.out.println("呼叫Singleton的私有構造器");
		
	}
	/**
	 * 用同步程式碼塊的方式,在判斷單例是否存在的if方法裡使用同步程式碼塊,在同步程式碼塊中再次檢查是否單例已經生成,
	 * 這也就是網上說的 雙重檢查加鎖的方法
	 * @return
	 */
	public static synchronized Singleton getInstance(){
		if(instance==null){
			synchronized (Singleton.class) {
				if(instance==null){
					instance = new Singleton();
				}
			}
		}
		return instance;
	}
	/**
	 * 
	 * 防止反序列生成新的單例物件,這是effective Java 一書中說的用此方法可以防止,具體細節我也不明白
	 * @return
	 */
	private Object readResolve(){
		return instance;
	}
	public static void main(String[] args) throws Exception {
		test1();
		test2();
	}
	/**
	 * 測試 反序列 仍然為單例模式
	 * @throws Exception
	 */
	public static void test2() throws Exception{
		Singleton s  = Singleton.getInstance();
		ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File("E:\\Singleton.txt")));
		objectOutputStream.writeObject(s);
		ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("E:\\Singleton.txt")));
		Object readObject = objectInputStream.readObject();
		Singleton s1 = (Singleton)readObject;
		System.out.println("s.hashCode():"+s.hashCode()+",s1.hashCode():"+s1.hashCode());
		
		objectOutputStream.flush();
		objectOutputStream.close();
		objectInputStream.close();
	}
	/**
	 * 測試反射攻擊
	 * @throws Exception
	 */
	public static void test1(){
		Singleton s  = Singleton.getInstance();
		Class c = Singleton.class;
		Constructor privateConstructor;
		try {
			privateConstructor = c.getDeclaredConstructor();
			privateConstructor.setAccessible(true);
			privateConstructor.newInstance();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}


驗證反射攻擊結果:

如果不新增readResolve方法的結果:

新增readResolve方法的結果: