有時候,我們希望某一個類只能產生一個物件。比如說一個學校的校長只能有一個。
初步#
要一個類只能有一個實例,也就是說我們要把它的構造器隱藏起來:private Something(){}
然後提供一個對外的方法,用來實際的產生 / 返回物件。也就是說我們可以得到下邊的實現:
public class Something {
private static Something INSTANCE = null;
private Something(){}
public static Something getINSTANCE() {
if (INSTANCE == null){
INSTANCE = new Something();
}
return INSTANCE;
}
}
這是一個單例模式的簡單實現。我們發現,每次getINSTANCE()
的時候,都要判斷INSTANCE == null
。
所以我們也可以這樣(如果你的單例物件足夠小,而且運行期間都需要):
public class Something {
private static Something INSTANCE = new Something();
private Something(){}
public static Something getINSTANCE() {
return INSTANCE;
}
}
多線程下的單例模式#
上邊的單例模式實現在多線程下是不安全的。因為不能保證INSTANCE
的值不會改變。解決線程問題的方法,很簡單,加鎖。
比如,我們給getINSTANCE
方法加鎖。:
public static synchronized Something getINSTANCE() {
if (INSTANCE == null){
INSTANCE = new Something();
}
return INSTANCE;
}
或者在判斷 null 的時候加鎖:
public static Something getINSTANCE() {
synchronized (Something.class){
if (INSTANCE == null){
INSTANCE = new Something();
}
}
return INSTANCE;
}
但是,這樣有個問題,就是每次獲取實例的時候,都有鎖操作,會耗費資源,假如,這個方法被頻繁使用,會有性能問題。
把代碼改一下:
public static Something getINSTANCE() {
if (INSTANCE == null){
synchronized (Something.class){
if (INSTANCE == null){
INSTANCE = new Something();
}
}
}
return INSTANCE;
}
這樣寫的好處是:只有在INSTANCE
為 null 的時候才會加鎖處理。也就是所只起作用一次。
使用枚舉實現單例#
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
使用枚舉來實現單例,在類真正被用到的時候才會加載物件(利用類加載機制),加載過程是同步的,是線程安全的。
前邊的單例在面對序列化、反序列化的時候還是不安全的,但是對於枚舉,直接規定不能反序列化枚舉物件。。。
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {
// 略
// 不允許反序列化枚舉物件
private void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
throw new InvalidObjectException("can't deserialize enum");
}
// 不允許反序列化枚舉物件
private void readObjectNoData() throws ObjectStreamException {
throw new InvalidObjectException("can't deserialize enum");
}
// 枚舉類不可以有finalize方法,子類不可以重寫該方法 保證實例的物件唯一
protected final void finalize() { }
}