最近的兩次面試中,都被要求在紙上寫代碼實現單例(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種枚舉方法。