目錄
單例模式是工作中高頻使用的設計模式之一。單例模式可以確保內存中單例類只有一個實例,有效的減少了內存的開銷,避免了類的重複創建和銷燬。
懶漢式爲什麼是線程不安全的
單例模式有兩種實現方式,即大家所熟悉的餓漢式和懶漢式。二者的區別是創建實例的時機,餓漢式在應用啓動時就創建了 實例,餓漢式是線程安全的,是絕對單例的。懶漢式在對外提供的獲取方法被調用時會實例化對象。在多線程情況下,懶漢模式不是線程安全的,示範代碼如下:
public class LazySingleTon {
private static LazySingleTon lazySingleTon = null;
private LazySingleTon(){
}
public static LazySingleTon getInstance(){
if(lazySingleTon == null){
//對下面的代碼加斷點
lazySingleTon = new LazySingleTon();
}
System.out.println(lazySingleTon);
return lazySingleTon;
}
public static void main(String[] args) {
for(int i = 1 ; i <= 2 ;i ++ ){
Thread thread = new ForTestThread();
thread.start();
}
}
}
public class ForTestThread extends Thread {
@Override
public void run() {
LazySingleTon lazySingleTon = LazySingleTon.getInstance();
}
}
操作如下
配合idea的多線程debug,執行main方法,在實例化處加斷點。當第一個線程執行驗證了lazySingleTon對象爲null,準備new一個新對象時第二個線程也驗證完了lazySingleTon爲null,這時兩個線程都會實例化LazySingleTon,執行完成輸出的內容:
Connected to the target VM, address: '127.0.0.1:49751', transport: 'socket'
com.rbac.console.singleton.LazySingleTon@492dccc0
Disconnected from the target VM, address: '127.0.0.1:49751', transport: 'socket'
com.rbac.console.singleton.LazySingleTon@2f17749c
打印內容顯示同一個內存中存在兩個實例分別是:LazySingleTon@2f17749c、LazySingleTon@492dccc0。這表明在多線程情況下有一定機率會出現一個單例類會有多個實例,證明懶漢式是非線程安全的。
懶漢模式實現線程安全
1.對實例化方法加鎖
public static synchronized LazySingleTon getInstance(){
if(lazySingleTon == null){
lazySingleTon = new LazySingleTon();
}
System.out.println(lazySingleTon);
return lazySingleTon;
}
加鎖可以確保實例話方法只能有一個線程執行,確保兩個線程不會同時進入實例化方法。缺點是同步鎖的加鎖和解鎖比較消耗資源,而且synched關鍵字修飾static方法時鎖的是整個class,對性能會有影響
2.double check+volatile方案
public static LazySingleTon getInstance(){
if(lazySingleTon == null){
synchronized (LazySingleTon.class) {
if (lazySingleTon == null) {
//1.分配內存
//2.初始化對象
//3.設置LazySingleTon類執行內存
lazySingleTon = new LazySingleTon();
}
}
}
System.out.println(lazySingleTon);
return lazySingleTon;
}
double check方案可以實現線程安全,且降低內存消耗。代碼中三行註釋代表了new對象時底層進行了三步操作,由於JVM優化算法可能會指令重排,也就是第二步和第三步執行的順序會互換。這樣可能會出現第一個第一個線程出現了指令重排情況,還沒來得及初始化對象,第二個線程就進行了對象獲取,導致獲取到的對象是null。
爲避免這種情況的發生可以使用volatile關鍵字修飾靜態類,禁止指令重排
private volatile static LazySingleTon lazySingleTon = null;
也可以使用靜態內部類的初始化延遲加載解決,靜態內部類有初始化鎖,達到線程安全的目的
public class LazySingleTon {
private LazySingleTon(){
}
public static LazySingleTon getInstance(){
return InnerClass.lazySingleTon;
}
private static class InnerClass{
private static LazySingleTon lazySingleTon = new LazySingleTon();
}
public static void main(String[] args) {
for(int i = 1 ; i <= 2 ;i ++ ){
Thread thread = new ForTestThread();
thread.start();
}
}
}
文章內容參考自慕課網
設計模式學習友情鏈接:
這位小可愛,如果覺得文章不錯,請關注或點贊 (-__-)謝謝