天天看點

面試:什麼是單例模式?有幾種實作方式?

作者:程式員的秃頭之路

單例模式是一種設計模式,它確定一個類隻能有一個執行個體,并提供了一個全局通路點來通路該執行個體。

在Java中,可以通過将類的構造函數設定為私有,以防止其他類執行個體化它,并提供一個靜态方法來擷取類的唯一執行個體。該方法在第一次被調用時建立執行個體,以後每次調用該方法時都傳回同一個執行個體。

以下是一個簡單的Java單例模式示例:

public class Singleton {

    private static Singleton instance;

    private Singleton() {
        //私有構造函數
    }

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
           

在這個示例中,Singleton類的構造函數是私有的,是以其他類不能執行個體化它。getInstance()方法是靜态的,每次調用時都會檢查instance是否為null。如果instance為null,則建立一個新的執行個體并将其指派給instance。如果instance不為null,則傳回現有執行個體。由于這個方法是同步的,是以隻有一個線程能夠通路它,進而確定隻建立一個執行個體。

1.懶漢式單例模式:

懶漢式單例模式是在需要時才建立單例執行個體。在該模式下,單例執行個體不會在程式啟動時立即被建立,而是在第一次請求時建立。以下是懶漢式單例模式的一個簡單實作:

public class Singleton {
    private static Singleton instance;

    private Singleton() {
        // 私有構造函數
    }

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
           

在上面的代碼中,instance是一個靜态變量,當第一次請求getInstance()方法時,會建立一個新的執行個體并将其指派給instance。之後,每次調用getInstance()方法時,都會傳回該執行個體。

2.餓漢式單例模式:

餓漢式單例模式是在程式啟動時就建立單例執行個體,無論是否需要該執行個體。

以下是餓漢式單例模式的一個簡單實作:

public class Singleton {
    private static final Singleton instance = new Singleton();

    private Singleton() {
        // 私有構造函數
    }

    public static Singleton getInstance() {
        return instance;
    }
}
           

在上面的代碼中,instance是一個靜态常量,它在類加載時就被建立并指派。由于該執行個體是靜态的,是以可以通過getInstance()方法來通路它。

下面是一個完整的單例模式示例,它使用懶漢式實作:

public class Singleton {
    private static Singleton instance;

    private Singleton() {
        // 私有構造函數
    }

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
           

在上面的示例中,instance是一個靜态變量,getInstance()方法是靜态的,是以可以在沒有建立執行個體的情況下調用它。該方法是同步的,以避免多個線程同時建立執行個體。在第一次調用getInstance()方法時,會建立一個新的執行個體并将其指派給instance。在之後的每次調用中,都會傳回現有執行個體。

3.雙重校驗鎖(Double Checked Locking):

雙重校驗鎖是一種常用的單例模式優化方式。它利用了懶加載和同步機制,隻在需要時才建立單例執行個體,并保證了線程安全。以下是雙重校驗鎖的一個簡單實作:

public class Singleton {
    private static volatile Singleton instance;

    private Singleton() {
        // 私有構造函數
    }

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
           

在上面的代碼中,instance變量使用了volatile關鍵字,以確定多個線程之間的可見性。雙重檢查鎖定是通過先檢查instance是否為null,然後才進入同步塊來實作的。在同步塊内部,再次檢查instance是否為null,如果為null則建立一個新執行個體。該實作方法既滿足了線程安全,又避免了不必要的同步操作。

4.枚舉(Enum):

枚舉模式在Java中是一種非常常用的單例模式實作方式,它的使用非常簡單。下面是枚舉模式的一個簡單示例:

public enum Singleton {
    INSTANCE;

    private String name;

    private Singleton() {
        this.name = "Singleton";
    }

    public String getName() {
        return name;
    }
}
           

在上面的代碼中,Singleton是一個枚舉類型,并且隻有一個枚舉常量INSTANCE,它是一個單例執行個體。枚舉常量在類加載時被初始化,并且隻會被初始化一次,是以枚舉實作單例模式是線程安全的。

可以通過Singleton.INSTANCE來擷取單例執行個體。在單例類中,可以定義一些成員變量和成員方法,這些成員變量和成員方法都是單例執行個體的屬性和行為。

下面是使用枚舉模式實作單例模式的一個示例:

public class Main {
    public static void main(String[] args) {
        Singleton singleton1 = Singleton.INSTANCE;
        Singleton singleton2 = Singleton.INSTANCE;

        System.out.println(singleton1 == singleton2); // 輸出true

        System.out.println(singleton1.getName()); // 輸出Singleton
        System.out.println(singleton2.getName()); // 輸出Singleton
    }
}
           

在上面的代碼中,建立了兩個單例執行個體singleton1和singleton2,它們都是通過枚舉常量Singleton.INSTANCE來擷取的。由于枚舉常量在類加載時被初始化,并且隻會被初始化一次,是以singleton1和singleton2是同一個執行個體。可以通過==運算符來判斷它們是否相等。

在單例類中,可以定義一些成員變量和成員方法,這些成員變量和成員方法都是單例執行個體的屬性和行為。在上面的示例中,定義了一個成員變量name和一個成員方法getName(),可以通過單例執行個體來通路它們。

總之,使用枚舉模式實作單例模式非常簡單,隻需要定義一個枚舉類型,并在其中定義一個枚舉常量,該枚舉常量就是單例執行個體。在需要擷取單例執行個體時,隻需要使用枚舉常量即可。

5.靜态内部類(Static Inner Class):

靜态内部類是一種比較常見的單例模式實作方式。它使用靜态内部類來儲存單例執行個體,并且隻有在需要時才加載該内部類。以下是靜态内部類實作單例模式的一個簡單示例:

public class Singleton {
    private Singleton() {
        // 私有構造函數
    }

    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}
           

在上面的代碼中,SingletonHolder是一個靜态内部類,它隻在需要時才會被加載。INSTANCE是在靜态内部類中建立的,這樣可以保證線程安全和懶加載。由于靜态内部類隻會被加載一次,是以可以避免建立多個執行個體的問題。

總之,以上五種方式都可以用來實作單例模式,并且都有各自的優缺點。選擇哪種實作方式,取決于具體的應用場景和需求。例如,如果需要在單例執行個體初始化時進行一些複雜的計算,可以使用靜态内部類實作懶加載;如果需要確定單例執行個體隻有一個,并且需要支援序列化和反序列化,可以使用枚舉實作單例模式。

此外,還可以通過其他方式來優化單例模式的實作,例如使用IoC容器或者使用ThreadLocal來保證線程安全等。不過,在實際應用中,建議根據具體需求和實際情況進行選擇和優化。

繼續閱讀