單例的三種可用寫法
阿新 • • 發佈:2018-12-29
單例的三種可用寫法
單例是通過程式碼的寫法達到一個例項在一個堆中永遠只有一個的目的,從而去除記憶體不必要的開銷或達到某些例項(例如資料庫連線池、常見框架中的控制-事物-持久層等)永遠唯一的目的。筆者總結了以下三種可用的單例寫法,懶漢/餓漢式的寫法不再贅述
雙重判定鎖
/**
* 雙重判定鎖
*/
public class Singleton {
private static volatile Singleton singleton;
private Singleton() {
super();
}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null)
singleton = new Singleton();
}
}
return singleton;
}
public static void main(String[] args) {
for (int i = 0; i < 1000; i++) {
Runnable run = () - > System.out.println(Singleton.getInstance());
run.run();
}
}
}
- 這種寫法是最安全的方案,注意Singleton前的volatile必須加上,一個物件的建立分為兩步:例項化物件和開闢記憶體。這兩步很可能會通過CPU的重排序執行機制發生先後順序變更(因為建立物件的過程已經被上鎖,是通過同一個執行緒執行的,且上下文沒有資料關聯),也就意味著可能先開闢記憶體再例項化物件,在A執行緒開闢記憶體但例項化物件前,B執行緒去獲取singleton是可以獲取到的,雖然singleton中沒有任何資料,那麼B執行緒在使用獲取到的物件時就會拋異常了。當然,這種問題出現的機率非常小。
靜態屬性
/**
* 作為靜態常量屬性的單例
*/
class Singleton2 {
public static final Singleton2 singleton2 = new Singleton2();
private Singleton2() {
super();
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
Runnable run = () -> System.out.println(Singleton2.singleton2);
run.run();
}
}
}
- 這種寫法是最簡便的方案,利用靜態域的特性,一個單例可以毫不費力地建立,但這種寫法將單例作為靜態屬性且在屬性載入時直接將其例項化的做法會產生無用的記憶體開銷,當然,如果例項不大的話也無關緊要了
靜態內部類
/**
* 作為靜態常量屬性且通過靜態內部類構造的單例
*/
class Singleton3 {
private Singleton3() {
super();
}
private static class SintletonClassInstance {
private static final Singleton3 instance = new Singleton3();
}
public static Singleton3 getInstance() {
return SintletonClassInstance.instance;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
Runnable run = () -> System.out.println(Singleton3.getInstance());
run.run();
}
}
}
- 這種寫法是在靜態屬性的基礎上,通過靜態內部類懶載入的特性除去了無用記憶體開銷的弊端
至於選擇哪種方案,取決你自己的喜好和習慣