單例模式就是將構造方法私有化,然后提供一個獲取類實例的方法供外界調用,該方法保證該類任何時候都只有一個實例化的對象。為了保證類只有一個實例化對象,我們只要在類的內部持有一個自己的靜態實例,然后將此實例返回。
按照此種需求,我們就很容易寫出一下代碼:
懶漢方式,有線程不安全的問題
1 public class Singleton {
2 private static Singleton instance; //自己持有的靜態實例
3 private Singleton (){} //私有化構造方法,防止外界調用
4 public static Singleton getInstance() { //提供單例的對外方法
5 if (instance == null) {
6 instance = new Singleton();
7 }
8 return instance;
9 }
10 }
這就是懶漢方法的最直接實現,但是,代碼在執行的時候并非連續的,如果存在多線程時,此種方法就會有概率的出現問題。比如線程A在執行完第5行代碼后,線程B開始執行并產生一個實例,此后線程A繼續執行,則又會產生新的實例。
為了解決這個問題,我們很容易想到用synchronized將可能產生線程不安全的部分進行同步鎖定,代碼如下:
懶漢方式,線程安全
1 public class Singleton {
2 private static Singleton instance;
3 private Singleton (){}
4 public static synchronized Singleton getInstance() { //同步鎖
5 if (instance == null) {
6 instance = new Singleton();
7 }
8 return instance;
9 }
10 }
上面的方式是很粗暴的,因為我們一旦調用的getInstance方式,都會進入線程同步鎖定的狀態,其實在我們的單一實例初始化后就沒有線程安全的問題了。
改進后的方式如下:帶雙重校驗的懶漢方式
1 public class Singleton {
2 private volatile static Singleton singleton;
3 private Singleton (){}
4 public static Singleton getSingleton() {
5 if (singleton == null) {
6 synchronized (Singleton.class) {
7 if (singleton == null) {
8 singleton = new Singleton();
9 }
10 }
11 }
12 return singleton;
13 }
14 }
此種方法看似很好,但是,存在多線程不安全的情況很少,籠統的synchronized 會是一種效率很低的方法。有沒有其他的方式避免線程不安全呢。
Java的類加載器classloder,在加載class時自身就有保證線程安全的機制。我們可以將生成實例的代碼部分放在類加載的時候實現,借用classloder的機制,保證線程安全。
餓漢方式,在類加載時就生成所需的唯一實例
1 public class Singleton {
2 private static Singleton instance = new Singleton(); //靜態實例在類加載時就生成
3 private Singleton (){}
4 public static Singleton getInstance() {
5 return instance;
6 }
7 }
餓漢模式還有個變種,就是將實例化的代碼放在靜態代碼塊中,靜態代碼塊也會在類加載時就執行一次。
public class Singleton {
2 private Singleton instance = null;
3 static { //靜態代碼塊
4 instance = new Singleton();
5 }
6 private Singleton (){}
7 public static Singleton getInstance() {
8 return this.instance;
9 }
10 }
這種餓漢模式比懶漢模式要效率高,但是這種方式只要式類被加載了就會實例化對象,消耗資源,不管你是不是真的需要會調用getInstance()。如果我們想要在需要時(調用getInstance)再加載類,這樣就能既可以借用classLoader保證線程安全,又可以提高效率。如是,我們可以用一個內部類持有我們的單一實例,在需要時才會加載這個內部類。
靜態內部類方式:
1 public class Singleton {
2 private static class SingletonHolder {
3 private static final Singleton INSTANCE = new Singleton();
4 }
5 private Singleton (){}
6 public static final Singleton getInstance() {
7 return SingletonHolder.INSTANCE;
8 }
9 }
java1.5后引入了枚舉,利用枚舉也能實現單例模式.Effective Java中提倡使用這種方式
1 public enum Singleton {
2 INSTANCE;
3 public void whateverMethod() {
4 }
5 }