JAVA 中 單例和多例


背景:最近在學習韓老師的筆記時候發現不是很了解單例和多例,於是通過網上查找資料的方式去學習。

設計模式:最佳的實踐,是軟件開發人員在軟件開發過程中面臨一般解決方案,也就是開發的經驗總結。

單例模式(Singleton):是 Java 中最簡單的設計模式之一。這種類型的設計模式屬於創建型模式,它提供了一種創建對象的最佳方式。

這種模式涉及到一個單一的類,該類負責創建自己的對象,同時確保只有單個對象被創建。這個類提供了一種訪問其唯一的對象的方式,可以直接訪問,不需要實例化該類的對象。

單例類的特點:

  • 單例類只能有一個實例。
  • 單例類必須自己創建自己的唯一實例。
  • 單例類必須給所有其他對象提供這一實例。
  • 單例類的構造方法私有。(避免外部利用構造方法直接創建多個實例

多例模式(Multiton):作為對象的創建模式,多例模式中的多例類可以有多個實例,而且多例類必須自己創建、管理自己的實例,並向外界提供自己的實例。

多例類的特點

  • 多例類可有多個實例。
  • 多例類必須自己創建,管理自己的實例,並向外提供自己的實例。
  • 根據是否有實例上限分為有上限的多例類和無上限的多例類。

多例的單例模式的比較:

  • 單例模式和多例模式屬於對象模式。
  • 它們都不對外提供構造方法,即構造方法都為私有。
  • 單例模式的對象在整個系統中只有一份,多例模式可以有多個實例。
  • 單例是所有的請求都用一個對象來處理,比如service和dao層的對象通常都是單例的,而多例是每個請求用一個新的對象來處理,比如action;
  • 單例可以節約CPU和內存,多例可以防止並發問題,即一個請求改變了對象的狀態,此時對象處理另一個請求,而之前的請求對象狀態的改變導致對象對另一個請求的錯誤處理,對象中含有可改變的狀態時,則用多例反之單例。

單例的實現:

創建一個 SingleObject 類。SingleObject 類有它的私有構造函數和本身的一個靜態實例。SingleObject 類提供了一個靜態方法,供外界獲取它的靜態實例。

復制代碼
package ecut.enums;

public class SingleObject {

    // 創建 SingleObject 的一個對象
    private static SingleObject instance = new SingleObject();

    // 讓構造函數為 private,這樣該類就不會被實例化
    private SingleObject() {
    }

    // 獲取唯一可用的對象
    public static SingleObject getInstance() {
        return instance;
    }

    public void showMessage() {
        System.out.println("Hello World!");
    }
}
復制代碼

在SingletonPatternDemo中使用 SingleObject 類來獲取 SingleObject唯一 對象。

復制代碼
package ecut.enums;

public class SingletonPatternDemo {
    public static void main(String[] args) {

        // 不合法的構造函數
        // 編譯時錯誤:構造函數 SingleObject() 是不可見的
        // SingleObject object = new SingleObject();

        // 獲取唯一可用的對象
        SingleObject object = SingleObject.getInstance();

        // 顯示消息
        object.showMessage();
    }
}
復制代碼

運行結果如下:

Hello World!

 單例的幾種實現方法:

1、懶漢式,線程不安全

是lazy初始化(懶加載,在需要的時候才創建單例對象分配內存,而不是隨着軟件系統的運行或者當類被加載器加載的時候就創建),多線程不安全,容易實現,沒有加鎖synchronized,因此不支持多線程。

復制代碼
package ecut.enums;

/**
 * "懶漢"式,線程不安全實現單例 ( Singleton )
 */
public class Sun {
    
    // 1、將所有構造方法私有 (無法在類外正常創建該類實例、該類無子類)
    private Sun(){
    }
    
    // 3、提供一個靜態變量緩存那個惟一的實例
    private static Sun s ;

    // 2、通過靜態方法來獲得該類的惟一的實例
    public static Sun getInstance() {
        if( s == null ) {
            s = new Sun();
        }
        return s ;
    }
}
復制代碼

2、懶漢式,線程安全

是lazy初始化,多線程安全,容易實現,具備很好的lazy loading,能夠在多線程中很好的工作,但是效率低,99%情況下不需要同步。

優點:第一次調用才初始化,避免內存浪費。
缺點:必須加鎖 synchronized 才能保證單例,但加鎖會影響效率。getInstance() 的性能對應用程序不是很關鍵(該方法使用不太頻繁)。

復制代碼
package ecut.enums;

/**
 * "懶漢"式,線程安全實現單例 ( Singleton )
 */
public class Sun {
    
    // 1、將所有構造方法私有 (無法在類外正常創建該類實例、該類無子類)
    private Sun(){
    }
    
    // 3、提供一個靜態變量緩存那個惟一的實例
    private static Sun s ;

    // 2、通過靜態方法來獲得該類的惟一的實例,synchronized 關鍵字聲明的方法同一時間只能被一個線程訪問。
    public static synchronized Sun getInstance() {
        if( s == null ) {
            s = new Sun();
        }
        return s ;
    }
}
復制代碼

使用synchronized關鍵字修飾靜態方法達到線程安全的原理: 對共享資源進行訪問的方法定義中加上synchronized關鍵字修飾,此方法稱為同步方法,可以簡單理解成對此方法進行了加了內置鎖,內置鎖是當前的Class字節碼對象,多線程環境下,當執行此方法時,首先都要獲得此同步鎖(且同時最多只有一個線程能夠獲得),只有當線程執行完此同步方法后,才會釋放鎖對象,其他的線程才有可能獲取此同步鎖,實現了線程同步。同步了線程就安全。

線程同步:同步就是協同步調,按預定的先后次序進行運行。

線程安全:多個線程在執行同一段代碼的時候,每次的執行結果和單線程執行的結果都是一樣的,不存在執行結果的二義性,就可以稱作是線程安全的。

內置鎖: 在Java中任何一個對象都可以用作於同步鎖,而這些鎖就是內置鎖,線程進入同步代碼塊之前自動獲取到鎖,代碼塊執行完成正常退出或代碼塊中拋出異常退出時會釋放掉鎖,內置鎖為互斥鎖,即線程A獲取到鎖后,線程B阻塞直到線程A釋放鎖,線程B才能獲取到同一個鎖。

內置鎖在Java語言中的表現:多線程的鎖,其實本質上就是給一塊內存空間的訪問添加訪問權限,因為Java中是沒有辦法直接對某一塊內存進行操作的,又因為Java是面向對象的語言,一切皆對象,所以具體的表現就是某一個對象承擔鎖的功能,每一個對象都可以是一個鎖。內置鎖,使用方式就是使用 synchronized 關鍵字,synchronized 方法或者 synchronized 代碼塊。

 3、餓漢式

不是lazy初始化,多線程安全,容易實現,比較常用,但容易產生垃圾對象。

優點:比較常用,但容易產生垃圾對象。
缺點:類加載時就初始化,浪費內存(餓漢式單例在類被加載時就創建單例對象並且長駐內存,不管你需不需要它;如果單例類占用的資源比較多,就會降低資源利用率以及程序的運行效率)。

它基於 classloder 機制避免了多線程的同步問題,不過,instance 在類裝載時就實例化,雖然導致類裝載的原因有很多種,在單例模式中大多數都是調用 getInstance 方法, 但是也不能確定有其他的方式(或者其他的靜態方法)導致類裝載,這時候初始化 instance 顯然沒有達到 lazy loading 的效果。

復制代碼
package ecut.enums;

/**
 * "餓漢"式實現單例 ( Singleton )
 */
public class Moon {
    
    // 1、將所有構造方法私有 (無法在類外正常創建該類實例、該類無子類)
    private Moon(){
    }
    
    // 3、提供一個靜態變量緩存那個惟一的實例
    private static final Moon MOON  = new Moon();

    // 2、通過靜態方法來獲得該類的惟一的實例
    public static Moon getInstance() {
        return MOON ;
    }
}
復制代碼

4、雙檢鎖/雙重校驗(DCL,即double-checked locking)

jdk1.5起,是lazy初始化,多線程安全,實現較復雜,這種方式采用雙鎖機制,安全且在多線程情況下能保持高性能。getInstance() 的性能對應用程序很關鍵。

復制代碼
package ecut.enums;

/**
 * 
 *  雙檢鎖實現單例 ( Singleton )
 *
 */
public class Singleton {  
    /*volatile:修飾成員變量在被每次被線程訪問時,都強制從共享內存中讀取該成員變量的值,而且當成員變量發生變化時,會強制線程將變化值寫到共享內存,
    這樣在任何時刻兩個不同的線程總是看到某個成員變量的同一個值。*/
    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;  
    }  
}  
復制代碼

5、登記式/靜態內部類

是lazy初始化,多線程安全。

利用了ClassLoader的機制保證了線程安全;不同的是,餓漢式在類被加載時就創建了一個實例對象,而靜態內部類即使類被加載也不會創建單例對象,除非調用里面的getInstance()方法。因為當Singleton類被加載時,其靜態內部類SingletonHolder沒有被主動使用。只有當調用getInstance方法時,才會裝載SingletonHolder類,從而實例化單例對象。這樣,通過靜態內部類的方法就實現了lazy loading,很好地將懶漢式和餓漢式結合起來,既實現延遲加載,保證系統性能,也能保證線程安全。

復制代碼
public class Singleton {  
    private static class SingletonHolder {  
    private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
    return SingletonHolder.INSTANCE;  
    }  
}   
復制代碼

6、枚舉

jdk1.5起,不是lazy初始化,多線程安全,容易實現,這種實現方式還沒有被廣泛采用,但這是實現單例模式的最佳方法。它更簡潔,自動支持序列化機制,絕對防止多次實例化。它不僅能避免多線程同步問題,而且還自動支持序列化機制,防止反序列化重新創建新的對象,絕對防止多次實例化。不過,由於 JDK1.5 之后才加入 enum 特性,用這種方式寫不免讓人感覺生疏,在實際工作中,也很少用。

public enum Singleton {  
    INSTANCE;  
    public void whateverMethod() {  
    }  
}  

多例的實現:

復制代碼
package ecut.enums;


/**
 * 實現多例 ( Multiton )
 */
public class Season {
    
    private int index ;
    private String name ;
    
    public static final Season SPRING = new Season( 1 , "春暖花開時" );
    public static final Season SUMMER = new Season( 2 , "炎炎夏日" );
    public static final Season AUTUMN = new Season( 3 , "又該吃月餅了" );
    public static final Season WINTER = new Season( 4 , "美麗凍人" );
    
    private Season(int index, String name) {
        super();
        this.index = index;
        this.name = name;
    }

    public int getIndex() {
        return index;
    }
    
    public String getName() {
        return name;
    }

}
復制代碼

待解決問題:

ClassLoader機制

轉載處:http://www.cnblogs.com/AmyZheng/p/8443914.html

 

 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM