1. 手寫一個Singleton
1.1. 餓漢式
-
特點:
在調用方法前,實例已經被創建了。 -
java實現
class Singleton{
//缺點:不能有其他實例變量,因爲getInstance方法沒有同步所以會出現非線程安全問題。
private static Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
- c實現
後續補充…
- c++實現
Singleton *Singleton::instance = new Singleton();
Singleton* getInstance()
{
return instance;
}
-
python實現
後續補充…
1.2. 懶漢式
- 特點:
延遲加載就是在調用get()方法時實例才被創建。
- java實現
class Singleton{
private static Singleton instance = null;
private Singleton(){}
//懶漢式必須要加上synchronize同步鎖關鍵字,否則線程不安全。
//缺點是效率低,public static synchronized 全部代碼加上了鎖等同於synchronized(Singleton.class)
public static synchronized Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
-
c實現
後續補充…
-
c++實現
Singleton* getInstance()
{
lock();
if (instance == NULL)
{
instance = new Singleton();
}
unlock();
return instance;
}
-
python實現
後續補充…
1.3. 雙重校驗鎖(改造版的懶漢式)
-
特點:
- 進行兩次null檢查提升了併發度,因爲在單例中new的情況非常少,絕大多數都是可以並行的讀操作。因此在加鎖前多進行一次null檢查就可以減少絕大多數的加鎖操作,執行效率提高的目的也就達到了。
- volatile關鍵字保證了各線程對singleton靜態實例域修改的可見性
- volatile禁止指令重排序優化。提高了多線程情況下的安全性
- java實現
public class Singleton {
private volatile static Singleton singleton;
private Singleton() {}
public static Singleton getSingleton() {
if (singleton == null) {
//此處加鎖
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
- c++實現
Singleton* getInstance()
{
if (instance == NULL)
{
lock();
if (instance == NULL)
{
instance = new Singleton();
}
unlock();
}
return instance;
}
1.4. 靜態內部類實現單例(線程安全、效率高)
- 特點:
- 同時具備線程安全和效率高特點
- 這種方式下 Singleton 類被裝載了,instance 不一定被初始化。因爲 SingletonHolder 類沒有被主動使用,只有通過顯式調用 getInstance 方法時,纔會顯式裝載 SingletonHolder 類,從而實例化 instance。
- java實現
public class Singleton {
private static class SingletonHolder {
//注意內部類SingletonHolder要用static修飾且其中的靜態變量INSTANCE必須是final的
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
以上四種都有兩個問題:
1. 都需要額外的工作(Serializable、transient、readResolve())來實現序列化,否則每次反序列化一個序列化的對象實例時都會創建一個新的實例。
2. 可能會有人使用反射強行調用我們的私有構造器(如果要避免這種情況,可以修改構造器,讓它在創建第二個實例的時候拋異常)。
解決方法:
實現 Serializable接口。並重寫readResolve()方法
@override
protected Object readResolve() throws ObjectStreamException{
return SingletonHolder.INSTANCE;
}
1.4. 枚舉寫法實現單利(Effective Java推薦)
-
特點:
使用枚舉除了線程安全和防止反射強行調用構造器之外,還提供了自動序列化機制,防止反序列化的時候創建新的對象
- java實現
// 定義單例模式中需要完成的代碼邏輯
public interface MySingleton {
void doSomething();
}
public enum Singleton implements MySingleton {
INSTANCE {
@Override
public void doSomething() {
System.out.println("complete singleton");
}
};
public static MySingleton getInstance() {
return Singleton.INSTANCE;
}
}