JavaNotes00-SingletonPattern(單例總結)

wei-spring發表於2015-02-08

轉:http://hukai.me/java-notes-singleton-pattern/

 

這裡不贅述單例模式的概念了,直接演示幾種不同的實現方式。

0)Eager initialization

如果程式一開始就需要某個單例,並且建立這個單例並不那麼費時,我們可以考慮用這種方式:

1
2
3
4
5
6
7
8
9
public class Singleton {
    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return INSTANCE;
    }
}

這種實現方式有幾個特點:

  • 例項一開始就被建立(Eager initialization)。
  • getInstance()方法不需要加synchronize來解決多執行緒同步的問題。
  • final關鍵字確保了例項不可變,並且只會存在一個。

1)Lazy initialization

懶載入的方式使得單例會在第一次使用到時才會被建立.先看一種有隱患的寫法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public final class LazySingleton {
  private static volatile LazySingleton instance = null;

  // private constructor
  private LazySingleton() {
  }

  public static LazySingleton getInstance() {
      if (instance == null) {
          synchronized (LazySingleton.class) {
              instance = new LazySingleton();
          }
      }
      return instance;
  }
}

請注意:上面的寫法其實非執行緒安全的,假設兩個Thread同時進入了getInstance方法,都判斷到instance==null,然後會因為synchronized的原因,逐個執行,這樣就得到了2個例項。解決這個問題,需要用到典型的double-check方式,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class LazySingleton {
    private static volatile LazySingleton instance = null;

    private LazySingleton() {       
    }

    public static LazySingleton getInstance() {
        if (instance == null) {
            synchronized (LazySingleton .class) {
                if (instance == null) {
                        instance = new LazySingleton ();
                }
            }
        }
        return instance;
    }
}

另外一個更簡略直觀的替代寫法是:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class LazySingleton {
    private static volatile LazySingleton instance = null;

    private LazySingleton() {       
    }

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

2)Static block initialization

如果我們對程式的載入順序有點了解的話,會知道Static block的初始化是執行在載入類之後,Constructor被執行之前。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class StaticBlockSingleton {
  private static final StaticBlockSingleton INSTANCE;

  static {
      try {
          INSTANCE = new StaticBlockSingleton();
      } catch (Exception e) {
          throw new RuntimeException("Error, You Know This, Haha!", e);
      }
  }

  public static StaticBlockSingleton getInstance() {
      return INSTANCE;
  }

  private StaticBlockSingleton() {
      // ...
  }
}

上面的寫法有一個弊端,如果我們類有若干個static的變數,程式的初始化卻只需要其中的1,2個的話,我們會做多餘的static initialization。

3)Bill Pugh solution

University of Maryland Computer Science researcher Bill Pugh有寫過一篇文章initialization on demand holder idiom

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Singleton {
    // Private constructor prevents instantiation from other classes
    private Singleton() { }

    /**
    * SingletonHolder is loaded on the first execution of Singleton.getInstance() 
    * or the first access to SingletonHolder.INSTANCE, not before.
    */
    private static class SingletonHolder {
        public static final Singleton INSTANCE = new Singleton();
    }

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

SingletonHolder類會在你需要的時候才會被初始化,而且它不影響Singleton類的其他static成員變數的使用。這個方法是執行緒安全的並且避免了使用volatile與synchronized。

4)Using Enum

這是最簡便安全的方法。沒有明顯的缺點,並且避免了下面要講到的序列化的隱患。

1
2
3
4
5
6
public enum Singleton {
    INSTANCE;
    public void execute (String arg) {
        // perform operation here 
    }
}

Serialize and de-serialize

在某些情況下,需要實現序列化的時候,普通的單例模式需要新增readResolve的方法,不然會出現異常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class DemoSingleton implements Serializable {
  private volatile static DemoSingleton instance = null;

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

  protected Object readResolve() {
      return instance;
  }

  private int i = 10;

  public int getI() {
      return i;
  }

  public void setI(int i) {
      this.i = i;
  }
}

僅僅有上面的還不夠,我們需要新增serialVersionUID,例子詳見下面的總結。

Conclusion

實現一個功能完善,效能更佳,不存在序列化等問題的單例,建議使用下面兩個方式之一:

Bill Pugh(Inner Holder)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class DemoSingleton implements Serializable {
  private static final long serialVersionUID = 1L;

  private DemoSingleton() {
      // private constructor
  }

  private static class DemoSingletonHolder {
      public static final DemoSingleton INSTANCE = new DemoSingleton();
  }

  public static DemoSingleton getInstance() {
      return DemoSingletonHolder.INSTANCE;
  }

  protected Object readResolve() {
      return getInstance();
  }
}

Enum

1
2
3
4
5
6
public enum Singleton {
    INSTANCE;
    public void execute (String arg) {
        // perform operation here 
    }
}

參考資料


相關文章