首頁  >  文章  >  Java  >  Java單例模式的餓漢與懶漢模式實作方式詳解

Java單例模式的餓漢與懶漢模式實作方式詳解

PHPz
PHPz轉載
2023-04-27 12:40:071403瀏覽

什麼是單例模式

保證某個類別在程式中只存在一份實例,而不會建立多個實例,這樣就會提高效率。

在單利模式中一般只提供一個getInstance()方法來取得實例對象,不提供setInstance()方法,目的是為了避免再實例化出其他實例對象。

其中單例模式中有兩種模式一種是餓漢模式,一種是懶漢模式。

一.餓漢模式

1.餓漢模式的概念

餓漢模式就是在類別載入的時候立刻會實例化,後續使用只會出現一份實例。

2.餓漢模式代碼
package thread.example;
//饿汉模式
public class HungrySingle {
//在类加载的时候就实例化了,类加载只有一次,所以值实例化出了一份该实例对象
    private static HungrySingle instance = new HungrySingle();
    public static HungrySingle getInstance() {
        return instance;
    }
}
3.多執行緒是否執行緒安全

在類別載入的時候就已經實例化了,所以該實例化沒有涉及到實例化的修改操作,只是進行讀取操作。在多線程情況下是線程安全的。

二.懶漢模式

1.懶漢模式的概念

在類別載入的時候沒有直接實例化,而是呼叫指定實例方法的時候再進行實例化,這樣就能確保不想使用的時候也不會實例化。一般來說比餓漢模式的效率高。

2.單執行緒情況下的懶漢模式
package thread.example;
//单线程的懒汉模式
public class LazySingle {
    private static LazySingle instance = null;
    //只有在调用该方法的时候才实例化
    public static LazySingle getInstance() {
        if(instance == null) {
            instance = new LazySingle();
        }
        return instance;
    }
}
3.多執行緒情況下的懶漢模式

(1)導致懶漢模式在多執行緒情況下的不安全性原因

在多線程的情況下,由於可能兩個線程都會得到一份instance=null,這是因為如果線程1修改了自己縣城中的instance後還沒來得及修改主內存中的instance,所導致線程2也實例化出了一份instance對象,這時候也就不再是單例模式了。主要導致該問題的是由於這裡面涉及了對instance的修改操作,失去了原子性,為了保證原子性,我們想到了加鎖,從而實現線程安全問題。

(2)解決方法程式碼範例

版本1

package thread.example;
//多线程安全下的懒汉模式
    public class LazySingle {
        private LazySingle() {
    }
    private static LazySingle instance = null;
    //只有在调用该方法的时候才实例化
    public static synchronized LazySingle getInstance() {
        if (instance == null) {
            instance = new LazySingle();
        }
        return instance;
    }
}

版本1的程式碼雖然保證了執行緒安全,但是每次呼叫該方法時還是會出現加鎖解鎖問題,為了進一步優化,我們可以減少鎖的粒度來提高效率,因為加了鎖之後也就和高並發無緣無故了,但我們還是想提高效率,所以才會進行優化。

版本2

雙重if判斷加鎖提高效率

package thread.example;
 
public class SecurityLazyModle {
    private LazySingle() {
    }
    private static volatile SecurityLazyModle instance = null;//保证内存可见性,防止编译器过度优化(指令重排序)
    public static SecurityLazyModle getInstance() {
        if(instance == null) {
            synchronized (SecurityLazyModle.class) {
                if(instance == null) {
                    instance = new SecurityLazyModle();
                }
            }
        }
        return instance;
    }
}

版本2的解釋說明

第一層if是為了判斷當前是否已經把實例創建出來,第二層synchronized是為了使進入當前if中的線程來競爭鎖,當拿到鎖的線程進入到第三層if之後判斷是否為空,不為空就是實例化對象,然後再釋放鎖,釋放鎖之後,instance已經不為空了,後面的線程就被阻擋在了第三層if這裡了,之後再來訪問getInstance()方法,發現該instance已經不為空了,也就不用再搶佔鎖資源了,因為競爭鎖也消耗大量的時間。透過這樣處理,既保證了線程安全,也提高了效率。

這裡使用volatile是為了防止編譯器最佳化所導致的指令重排序,在進行new一個物件不是原子性操作,可以分成三步驟:

  1. 1.分配記憶體空間

  2. 2.實例化物件

  3. 3.給變數賦值

#對於上面的執行,如果1和3先執行了(假設2還沒完成),在第一層if外的線程這時候判斷不為null,這時候就會直接返回該對象,但是這個物件只執行了一半,之後使用就會導致線程安全問題。

透過volatile就可以確保這3步驟必須執行完(無論順序如何,最後都會執行完),外面的執行緒才可以執行,這時候就保證了該物件的完整性。

以上是Java單例模式的餓漢與懶漢模式實作方式詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:yisu.com。如有侵權,請聯絡admin@php.cn刪除