最近學習了一下單例模式,整理如下,如有錯誤和不足之處歡迎各位批評糾正,互相學習!
第一種:懶漢式,線程不安全
package singleton;
/**
* Created by MJ on 15/10/7.
*
* @use 懶漢,線程不安全, 在單進程的時候工作正常,但在多線程的情況下就不能正常工作。
*/
public class Singleton1 {
private static Singleton1 instance;
private Singleton1 () {}
public static Singleton1 getInstance () {
if (null == instance) {
instance = new Singleton1();
}
return instance;
}
}
懶漢式,線程不安全, 在單進程的時候工作正常,但在多線程的情況下就不能正常工作。如果兩個線程同時運行到判斷instance是否爲null的if語句,並且instance的確沒有創建時,那麼兩個線程都會創建一個實例,此時類型Singleton1就不再滿足單例模式的要求了。習!
package singleton;
/**
* Created by MJ on 15/10/7.
* @use 懶漢式,線程安全
*/
public class Singleton2 {
private static Singleton2 instance;
private Singleton2() {}
public static synchronized Singleton2 getInstance() {
if (null == instance) {
instance = new Singleton2();
}
return instance;
}
}
懶漢式,線程安全。如果兩個線程同時想創建一個實例。由於在一個時刻只有一個線程能得到同步鎖,當第一個線程加上鎖時,第二個線程只能等待。當第一個線程發現實例還沒有創建時,它創建出一個實例。接着第一個線程釋放同步鎖,此時第二個線程可以加上同步鎖,並運行接下來的代碼。這個時候由於實例已經被第一個線程創建出來了,第二個線程就不會重複創建實例了,這樣就保證了在多線程環境中也只能得到一個實例。
第三種:餓漢式
package singleton;
/**
* Created by MJ on 15/10/7.
*
* @use 餓漢式,類加載時就會實例化
*/
public class Singleton3 {
private static Singleton3 instance = new Singleton3();
private Singleton3() {}
public static Singleton3 getInstance() {
return instance;
}
}
這種方式基於classloder機制避免了多線程的同步問題,不過,instance在類裝載時就實例化,雖然導致類裝載的原因有很多種,在單例模式中大多數都是調用getInstance方法, 但是也不能確定有其他的方式(或者其他的靜態方法)導致類裝載,這時候會過早地實現單例模式,從而降低內存的使用效率。
第四種:餓漢,變種
package singleton;
/**
* Created by MJ on 15/10/7.
*
* @use 餓漢式變種,在類初始化時實例化instance
*/
public class Singleton4 {
private static Singleton4 instance = null;
static {
instance = new Singleton4();
}
private Singleton4() {}
public static Singleton4 getInstance() {
return instance;
}
}
表面上看起來差別挺大,其實更第三種方式差不多,是在類初始化即實例化instance
第五種:靜態內部類
package singleton;
/**
* Created by MJ on 15/10/7.
*
* @use 靜態嵌套類式,利用了classloder的機制來保證初始化instance時只有一個線程,而類被裝載時,instance不一定被初始化。
*/
public class Singleton5 {
private static class SingletonNested {
private static final Singleton5 INSTANCE = new Singleton5();
}
private Singleton5() {}
public static final Singleton5 getInstance() {
return SingletonNested.INSTANCE;
}
}
這種方式同樣利用了classloder的機制來保證初始化instance時只有一個線程,它跟第三種和第四種方式不同的是(很細微的差別):第三種和第四種方式是隻要Singleton類被裝載了,那麼instance就會被實例化(沒有達到lazy loading效果),而這種方式是Singleton類被裝載了,instance不一定被初始化。因爲SingletonHolder類沒有被主動使用,只有顯示通過調用getInstance方法時,纔會顯示裝載SingletonHolder類,從而實例化instance。想象一下,如果實例化instance很消耗資源,我想讓他延遲加載,另外一方面,我不希望在Singleton類加載時就實例化,因爲我不能確保Singleton類還可能在其他的地方被主動使用從而被加載,那麼這個時候實例化instance顯然是不合適的。這個時候,這種方式相比第三和第四種方式就顯得很合理。
第六種:雙重校驗鎖
package singleton;
/**
* Created by MJ on 15/10/7.
*
* @use 雙重校驗式
*/
public class Singleton6 {
private volatile static Singleton6 instance;
private Singleton6() {}
public static Singleton6 getInstance() {
if (null == instance) {
synchronized (Singleton6.class) {
if (null == instance) {
instance = new Singleton6();
}
}
}
return instance;
}
}
這個是第二種方式的升級版,俗稱雙重檢查鎖定,詳細介紹請查看:http://www.ibm.com/developerworks/cn/java/j-dcl.html
在JDK1.5之後,雙重檢查鎖定才能夠正常達到單例效果。
第七種:枚舉
package singleton;
/**
* Created by MJ on 15/10/7.
*
* @use 枚舉式
*/
public enum Singleton7 {
INSTANCE;
public void whateverMethod(){}
}
這種方式是Effective Java作者Josh Bloch 提倡的方式,它不僅能避免多線程同步問題,而且還能防止反序列化重新創建新的對象,可謂是很堅強的壁壘啊,不過,個人認爲由於1.5中才加入enum特性,用這種方式寫不免讓人感覺生疏,在實際工作中,我也很少看見有人這麼寫過。
對我個人來說,我比較喜歡第三種和第五種方式,簡單易懂,而且在JVM層實現了線程安全(如果不是多個類加載器環境),一般的情況下,使用第三種方式,只有在要明確實現lazy loading效果時纔會使用第五種方式,另外,如果涉及到反序列化創建對象時我會試着使用枚舉的方式來實現單例,不過,我一直會保證我的程序是線程安全的,而且我永遠不會使用第一種和第二種方式,如果有其他特殊的需求,我可能會使用第七種方式,畢竟,JDK1.5已經沒有雙重檢查鎖定的問題了。