單例模式講解

單例模式中有懶漢式和餓漢式兩種,具體些發可以參見以下的代碼
餓漢式:
/餓漢式單例類.在類初始化時,已經自行實例化
public class Singleton1 {
private Singleton1() {}
private static final Singleton1 single = new Singleton1();
//靜態工廠方法
public static Singleton1 getInstance() {
return single;
}
}
餓漢式在類創建的時候就創建了一個靜態的類對象供系統使用,所以是線程安全的。

線程安全概念?

如果你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。如果每次運行結果和單線程運行的結果是一樣的,而且其他的變量的值也和預期的是一樣的,就是線程安全的。

或者說:一個類或者程序所提供的接口對於線程來說是原子操作,或者多個線程之間的切換不會導致該接口的執行結果存在二義性,也就是說我們不用考慮同步的問題,那就是線程安全的。


懶漢式:

//懶漢式單例類.在第一次調用的時候實例化自己
public class Singleton {
private Singleton() {}
private static Singleton single=null;
//靜態工廠方法
public static Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}
}
但是這種寫法沒有考慮到線程安全性問題,所以要解決線程安全性問題,可以通過以下三種方法:

1.在getInstance方法上加同步

public static synchronized Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}

2.雙重檢查鎖定

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

3.靜態內部類

public class Singleton {
private static class LazyHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return LazyHolder.INSTANCE;
}
}
這種比上面1、2都好一些,既實現了線程安全,又避免了同步帶來的性能影響。

下面以懶漢式爲例,寫一個例子:
public class TestSingleton {
String name = null;
private TestSingleton() {
}
private static volatile TestSingleton instance = null;
public static TestSingleton getInstance() {
if (instance == null) {
synchronized (TestSingleton.class) {
if (singleton == null) {
singleton = new TestSingleton();
}
}
}
return instance;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void printInfo() {
System.out.println("the name is " + name);
}
}
可以看到代碼中使用了關鍵字Volatile,具體原因如下所示:
假設沒有關鍵字volatile的情況下,兩個線程A、B,都是第一次調用該單例方法,線程A先執行instance = new Instance(),該構造方法是一個非原子操作,編譯後生成多條字節碼指令,由於JAVA的指令重排序,可能會先執行instance的賦值操作,該 操作實際只是在內存中開闢一片存儲對象的區域後直接返回內存的引用,之後instance便不爲空了,但是實際的初始化操作卻還沒有執行,如果就在此時線 程B進入,就會看到一個不爲空的但是不完整(沒有完成初始化)的Instance對象,所以需要加入volatile關鍵字,禁止指令重排序優化,從而安全的實現單例。


如果一個基本變量被volatile修飾,編譯器將不會把它保存到寄存器中,而是每一次都去訪問內存中實際保存該變量的位置上。這一點就避免了沒有 volatile修飾的變量在多線程的讀寫中所產生的由於編譯器優化所導致的災難性問題。所以多線程中必須要共享的基本變量一定要加上volatile修 飾符。當然了,volatile還能讓你在編譯時期捕捉到非線程安全的代碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章