單例模式是我們非常常用的設計模式之一。百度百科給出的定義:
單例模式,屬於創建類型的一種常用的軟件設計模式。通過單例模式的方法創建的類在當前進程中只有一個實例(根據需要,也有可能一個線程中屬於單例,如:僅線程上下文內使用同一個實例)
一般,我們爲了實現單例模式,通常把構造器私有化,然後通過靜態方法和靜態變量來獲取一個對象。
懶漢式,線程不安全
這種方式是最基礎的實現方式,最大的問題是不支持多線程,在多線程模式下會產生線程安全問題。
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
所以我們給getInstance()
加synchronized鎖,保證它在多線程的情況下使用。但會影響效率。
public class Singleton {
private Singleton singleton;
private Singleton(){};
public synchronized Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
餓漢式
比較常用的一種方法,使用classLoder機制避免了多線程同步問題。但是會產生垃圾對象,浪費內存。
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance() {
return instance;
}
}
雙重校驗鎖
採用懶加載模式,同樣也是線程安全的。採用雙鎖機制,在多線程模式下也能保持高性能
/**
* 懶漢式單例模式(線程不安全)
* */
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
使用volatile關鍵字
上面雙重校驗鎖可能會出現如下線程問題:
在 Singleton 構造函數體執行之前,變量 instance 可能成爲非 null 的,即賦值語句在對象實例化之前調用,此時別的線程得到的是一個還會初始化的對象, 也就是 一個線程走到3正在初始化時被掛起,另一個線程走到0,發現instance不爲null,就直接返回一個沒有初始化完全的對象!
所以爲了避免這種情況發生,我們給instance加上volatile關鍵字,利用它禁止指令重排序
的功能,給寫操作添加一個內存屏障。即在完全的初始化完一個對象前,不會調用讀操作。
public class Singleton {
private volatile static Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
靜態內部類登記
和餓漢式同樣,使用ClassLoader
機制來保證了初始化instance只有一個線程。而和餓漢式不一樣的地方在於,使用了Lazy初始化,只有在主動調用getInstance
方法時,纔會實例化對象。
/**
* 懶漢式單例模式(線程不安全)
* */
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton() {}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
枚舉
採用單例模式的最佳方法,簡介、自動支持序列化機制,而且避免了線程同步問題。不過目前很少被使用。
public enum Singleton {
INSTANCE;
public void whateverMethod() {}
}
使用ThreadLocal實現單例模式(線程安全)
ThreadLocal爲每個線程提供一個獨立的變量副本,從而隔離了多個線程對數據的訪問衝突。
/**
* 使用ThreadLocal實現單例模式(線程安全)
* */
public class Singleton {
private static final ThreadLocal<Singleton> tlSingleton = new ThreadLocal<Singleton>() {
@Override
protected Singleton initialValue() {
return new Singleton();
}
};
private Singleton(){}
public static Singleton getInstance() {
return tlSingleton.get();
}
}
使用CAS鎖實現
import java.util.concurrent.atomic.AtomicReference;
/**
* CAS
* */
public class Singleton {
private static final AtomicReference<Singleton> INSTANCE = new AtomicReference<>();
private Singleton(){}
public static Singleton getInstance() {
for (;;) {
Singleton current = INSTANCE.get();
if (current != null) {
return current;
}
current = new Singleton();
if (INSTANCE.compareAndSet(null , current)) {
return current;
}
}
}
}