使用單例模式的目的,是為了保證一個類只會創(chuàng)建一個對象,以避免產(chǎn)生多個對象消耗資源,或者某個對象本應(yīng)只有一個。
實現(xiàn)單例模式的主要要求有:
私有構(gòu)造方法
提供獲取對象的靜態(tài)方法
確保在對象的唯一性,尤其在多線程的情況下
確保對象在反序列化時不會重新構(gòu)建對象
示例
“餓漢式”
餓漢式會在一開始就創(chuàng)建好單例類的對象。然后提供獲取這個對象的方法。
public class A {
private static final A a = new A();
private A() {
}
public static A getInstance() {
return a;
}
}
“懶漢式”
懶漢式會在需要對象想創(chuàng)建這個對象,由于對象可能在多線程環(huán)境中,因此需要進(jìn)行同步,保證對象不會被多次創(chuàng)建。
示例一:
public class A {
private static A a = null;
private A() {
}
public static synchronized A getInstance() {
if (a == null) {
a = new A();
}
return a;
}
}
這種方式由于synchronized 關(guān)鍵字的存在,在訪問是會進(jìn)行加鎖,導(dǎo)致性能浪費。所以便有了Double Check Lock(DCL)實現(xiàn)單例的方式。
示例二:
public class A {
private static A a = null;
private A() {
}
public static A getInstance() {
if (a == null) {
synchronized (A.class) {
if (a == null) {
a = new A();
}
}
}
return a;
}
}
這種方式實現(xiàn)的單例,只會在第一次創(chuàng)建進(jìn)行加鎖,之后在獲取對象時,便不會加鎖,提高了性能。此外還可以使用靜態(tài)內(nèi)部類的方式實現(xiàn)單例。
示例三:
public class A {
private A() {
}
public static A getInstance() {
return SingletonHolder.a;
}
private static class SingletonHolder {
private static final A a = new A();
}
}
以上的幾種方式都實現(xiàn)了單例模式所要求的前三條,最后一條在反序列化時依然會重新構(gòu)造對象。所以要在類中加入readResolve私有方法,這個方法是一個可以讓開發(fā)者控制反序列化過程的方法,詳情查看關(guān)于 Java 對象序列化您不知道的 5 件事。以餓漢式為例。
示例四:
public class A implements Serializable {
private static final long serialVersionUID = 0L;
private static final A a = new A();
private A() {
}
public static A getInstance() {
return a;
}
private Object readResolve() throws ObjectStreamException {
return a;
}
}
除了上面的這種方法之外,還有一種更為簡單的方式實現(xiàn)反序列化且不重新構(gòu)造對象的方法,就是使用枚舉實現(xiàn)單例模式。
示例五:
public enum A {
INSTANCE;
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
class B {
public static void main(String[] args) {
A a = A.INSTANCE;
a.setName("AA");
System.out.println(a.getName());
}
}
枚舉類默認(rèn)創(chuàng)建時里是線程安全的,且在反序列化時也是單例。
參考資料:Android源碼設(shè)計模式,深度分析 Java 的枚舉類型,關(guān)于 Java 對象序列化您不知道的 5 件事。