面試中的單例模式

最近的兩次面試中,都被要求在紙上寫代碼實現單例(Singleton)模式。下文展示了三種不同的Singleton實現方式:


1.不好的解法一:只適用於單線程環境

public class Singleton1 {

	private static Singleton1 instance = null;

	private Singleton1() {

	}

	public static Singleton1 getInstance() {
		if (instance == null) {
			instance = new Singleton1();
		}
		return instance;
	}

}
這個解法只能適用於單線程的環境,在多線程環境中,可能會出現重複創建instance的情況


2.不好的解法二:雖然在多線程環境中能工作但效率不高
爲了保證在多線程環境中,每次只能有一個線程訪問getInstance()方法,我們用synchronized關鍵字修飾這個方法:

public class Singleton2 {

	private static Singleton2 instance = null;

	private Singleton2() {

	}

	public synchronized static Singleton2 getInstance() {
		if (instance == null) {
			instance = new Singleton2();
		}
		return instance;
	}

}
這樣雖然能夠在多線程環境下保證單例,但是Singleton2還不是十分完美,每次通過getInstance獲取實例的時候,都會試圖加上一個同步鎖,而加鎖是一個非常耗時的操作,在沒有必要的情況下我們應該儘量避免。


3.可行的解法:縮小同步鎖範圍

我們只是需要在實例還沒有創建之前需要加鎖操作,以保證只有一個線程創建出實例。而當實例已經創建之後,我們已經不需要再做加鎖操作了。

這裏我們使用Java中的Lock對象,並進行雙重校驗:

public class Singleton3 {

	private static Singleton3 instance = null;
	private static ReentrantLock lock = new ReentrantLock(false); // 創建可重入鎖,false代表非公平鎖

	private Singleton3() {

	}

	public static Singleton3 getInstance() {
		if (instance == null) {
			lock.lock();
			try {
				if (instance == null) {
					instance = new Singleton3();
				}
			} finally {
				lock.unlock();
			}
		}
		return instance;
	}

}

4.可行的解法:類裝載時初始化實例

public class Singleton4 {

	private static Singleton4 instance = new Singleton4();

	private Singleton4() {

	}

	public synchronized static Singleton4 getInstance() {
		return instance;
	}

}
這個方法是在類裝載時就初始化instance,雖然避免了多線程同步問題,但是沒有達到lazy loading的效果

5.推薦方法1:靜態內部類

public class Singleton5 {

	private Singleton5() {

	}

	private static class SingletonHolder {
		private static final Singleton5 INSTANCE = new Singleton5();
	}

	public static Singleton5 getInstance() {
		return SingletonHolder.INSTANCE;
	}

}
這種方法也能保證線程安全,而且也達到了lazy loading的效果


6.推薦方法2:枚舉

public enum Singleton6 {
	
	INSTANCE;
	
	public void whateverMethod() {

	}

}
這種方式是《Effective Java》作者Josh Bloch 提倡的方式,它不僅能避免多線程同步問題,而且還能防止反序列化重新創建新的對象


總結:
一般可以使用第4種和第5中方法。如果instance是個重量級的類,實例化時需要消耗很多資源,那麼這個時候就考慮第5中方法;

如果涉及反序列化創建對象時,我們考慮第6種枚舉方法。


發佈了83 篇原創文章 · 獲贊 25 · 訪問量 30萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章