餓漢模式
1.將構造方法私有化
2.私有靜態實例化
3.傳出構造完的私有實例
class Singletone {//餓漢模式
private static Singletone s = new Singletone();//必須在這裏實例化,靜態,私有
private Singletone() {//必須把構造函數申明爲私有
}
public static Singletone getSingleton() {//s爲static,函數也許static
return s;
}
}
非線程安全的飽漢模式
先判斷s是否已經初始化
class Singleton{
private static Singleton s1=null;
private Singleton(){
}
public static Singleton getsin(){
if(s1==null){
s1=new Singleton();
}
return s1;
}
}
線程安全的飽漢模式
class Singleton{
private static Singleton s1=null;
private Singleton(){
}
public synchronized static Singleton getsin(){//同步方法的
if(s1==null){
s1=new Singleton();
}
return s1;
}
}
減少鎖爭奪的懶漢模式(但有雙重檢查鎖定問題)
class Singleton {
private static Singleton s1 = null;
private Singleton() {
}
public static Singleton getsin() {
if (s1 == null) {
synchronized ("lock") {//上一個方法必須進入同步塊,而本方法避免了不必要的鎖獲取(再判斷不爲空時)
if (s1 == null) {
s1 = new Singleton();//問題根源 Q1
}
}
}
return s1;
}
}
雖然該方式儘可能避免了鎖爭奪的問題,但是依然會有線程安全問題
Q1行代碼其實可以分解成三步
1.分配s1的內存空間
2.用分配的內存初始化對象
3.將內存對象賦值給s1
其中2.3可能發生重排序,當把內存已經賦值給s1時(也就是s1不爲null時),初始化對象還未完成。因此是不安全的。
解決方案有兩種
1.將s1聲明未volatile(避免重排序)
2.使用類初始化(可重排序,但對外界不可見)
class Singletonclass{
private Singletonclass(){//單例一定要私有化構造函數!
}
private static class single{
public static Singletonclass s1=new Singletonclass();
}
public static Singletonclass getsin(){
return single.s1;
}
}
具體見併發編程的藝術第三章-三個同步原語-雙重檢查鎖定錯誤分析+兩種安全延遲初始化方案