1.單例模式介紹
所謂類的單例設計模式,就是 采取一定的方法保證在整個的軟體系統中,對某個類隻能存在一個對象執行個體,并且該類隻提供一個取得其對象執行個體的方法(靜态方法)。比如 Hibernate 的 SessionFactory,它充當資料存儲源的代理,并負責建立 Session 對象。SessionFactory 并不是輕量級的,一般情況下,一個項目通常隻需要一個 SessionFactory 就夠,這是就會使用到單例模式。
2.單例設計模式八種方式
- 餓漢式( 靜态常量) (單線程可用)
- 餓漢式(靜态代碼塊)(單線程可用)
- 懶漢式(線程不安全)
- 懶漢式(線程安全,同步方法)
- 懶漢式(線程安全,同步代碼塊)
- 雙重檢查 (推薦使用)
- 靜态内部類 (推薦使用)
- 枚舉 (推薦使用)
3.實作及說明
1. 餓漢式(靜态常量)
餓漢式(靜态常量)應用執行個體
步驟如下:
1) 構造器私有化 (防止 new )
2) 類的内部建立對象
3) 向外暴露一個靜态的公共方法。getInstance
4) 代碼實作
public class SingletonTest01 {
public static void main(String[] args) {
//測試
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance == instance2); // true
System.out.println("instance.hashCode=" + instance.hashCode());
System.out.println("instance2.hashCode=" + instance2.hashCode());
}
}
//餓漢式(靜态變量)
class Singleton {
//1. 構造器私有化
private Singleton() {
}
//2.本類内部建立對象執行個體
private final static Singleton instance = new Singleton();
//3. 提供一個公有的靜态方法,傳回執行個體對象
public static Singleton getInstance() {
return instance;
}
}
優缺點說明:
- 優點:這種寫法比較簡單,就是在類裝載的時候就完成執行個體化。避免了線程同步問題。
- 缺點:在類裝載的時候就完成執行個體化,沒有達到 Lazy Loading 的效果。如果從始至終從未使用過這個執行個體,則會造成記憶體的浪費
- 這種方式基于 classloder 機制避免了多線程的同步問題,不過,instance 在類裝載時就執行個體化,在單例模式中大多數都是調用 getInstance 方法,但是導緻類裝載的原因有很多種,是以不能确定有其他的方式(或者其他的靜态方法)導緻類裝載,這時候初始化 instance 就沒有達到 lazy loading 的效果
- 結論:這種單例模式可用,可能造成記憶體浪費(單線程可用)
2. 餓漢式(靜态代碼塊)
public class SingletonTest02 {
public static void main(String[] args) {
//測試
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance == instance2); // true
System.out.println("instance.hashCode=" + instance.hashCode());
System.out.println("instance2.hashCode=" + instance2.hashCode());
}
}
//餓漢式(靜态代碼塊)
class Singleton {
//1. 構造器私有化
private Singleton() {
}
//2.本類内部建立對象執行個體
private static Singleton instance;
static { // 在靜态代碼塊中,建立單例對象
instance = new Singleton();
}
//3. 提供一個公有的靜态方法,傳回執行個體對象
public static Singleton getInstance() {
return instance;
}
}
優缺點說明:
- 這種方式和上面的方式其實類似,隻不過将類執行個體化的過程放在了靜态代碼塊中,也是在類裝載的時候,就執行靜态代碼塊中的代碼,初始化類的執行個體。優缺點和上面是一樣的。
- 結論:這種單例模式可用,但是可能造成記憶體浪費
3. 懶漢式(線程不安全)
public class SingletonTest03 {
public static void main(String[] args) {
System.out.println("懶漢式1 , 線程不安全~");
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance == instance2); // true
System.out.println("instance.hashCode=" + instance.hashCode());
System.out.println("instance2.hashCode=" + instance2.hashCode());
}
}
class Singleton {
private static Singleton instance;
private Singleton() {}
//提供一個靜态的公有方法,當使用到該方法時,才去建立 instance
//即懶漢式
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
優缺點說明:
- 起到了 Lazy Loading 的效果,但是隻能在單線程下使用。
- 如果在多線程下,一個線程進入了 if (singleton == null)判斷語句塊,還未來得及往下執行,另一個線程也通過了這個判斷語句,這時便會 産生多個執行個體。是以在多線程環境下不可使用這種方式
- 結論:在實際開發中, 不要使用這種方式.
4. 懶漢式(線程安全,同步方法)
public class SingletonTest04 {
public static void main(String[] args) {
System.out.println("懶漢式2 , 線程安全~");
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance == instance2); // true
System.out.println("instance.hashCode=" + instance.hashCode());
System.out.println("instance2.hashCode=" + instance2.hashCode());
}
}
// 懶漢式(線程安全,同步方法)
class Singleton {
private static Singleton instance;
private Singleton() {}
//提供一個靜态的公有方法,加入同步處理的代碼,解決線程安全問題
//即懶漢式
public static synchronized Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
優缺點說明:
- 解決了線程安全問題
- 效率太低了,每個線程在想獲得類的執行個體時候,執行 getInstance()方法都要進行同步。而其實這個方法隻執行一次執行個體化代碼就夠了,後面的想獲得該類執行個體,直接 return 就行了。 方法進行同步效率太低
- 結論:在實際開發中, 不推薦使用這種方式
5. 懶漢式(線程安全,同步代碼塊)
class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if(instance == null) {
synchronized(Singleton.class){
instance = new Singleton();
}
}
return instance;
}
}
優缺點說明:
- 這種方式,本意是想對第四種實作方式的改進,因為前面同步方法效率太低,改為同步産生執行個體化的的代碼塊
- 但是這種同步并不能起到線程同步的作用 。跟第3種實作方式遇到的情形一緻,假如一個線程進入了if (singleton == null)判斷語句塊,還未來得及往下執行,另一個線程也通過了這個判斷語句,這時便會産生多個執行個體
- 結論:在實際開發中, 不能使用這種方式
6. 雙重檢查
public class SingletonTest06 {
public static void main(String[] args) {
System.out.println("雙重檢查");
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance == instance2); // true
System.out.println("instance.hashCode=" + instance.hashCode());
System.out.println("instance2.hashCode=" + instance2.hashCode());
}
}
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;
}
}
優缺點說明:
- Double-Check 概念是多線程開發中常使用到的,如代碼中所示,我們進行了兩次 if (singleton == null)檢查,這樣就可以保證線程安全了。
- 這樣,執行個體化代碼隻用執行一次,後面再次通路時,判斷 if (singleton == null),直接 return 執行個體化對象,也避免的反複進行方法同步.
- 線程安全; 延遲加載; 效率較高
- 結論:在實際開發中, 推薦使用這種單例設計模式
7.靜态内部類
public class SingletonTest07 {
public static void main(String[] args) {
System.out.println("使用靜态内部類完成單例模式");
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance == instance2); // true
System.out.println("instance.hashCode=" + instance.hashCode());
System.out.println("instance2.hashCode=" + instance2.hashCode());
}
}
// 靜态内部類完成, 推薦使用
class Singleton {
private static volatile Singleton instance;
//構造器私有化
private Singleton() {}
//寫一個靜态内部類,該類中有一個靜态屬性 Singleton
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
//提供一個靜态的公有方法,直接傳回SingletonInstance.INSTANCE
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
優缺點說明:
- 這種方式采用了類裝載的機制來保證初始化執行個體時隻有一個線程。
- 靜态内部類方式在 Singleton 類被裝載時并不會立即執行個體化,而是在需要執行個體化時,調用getInstance 方法,才會裝載 SingletonInstance 類,進而完成 Singleton 的執行個體化。
- 類的靜态屬性隻會在第一次加載類的時候初始化,是以在這裡,JVM 幫助我們保證了線程的安全性,在類進行初始化時,别的線程是無法進入的。
- 優點: 避免了線程不安全,利用 靜态内部類特點實作延遲加載,效率高
- 結論: 推薦使用.
8.枚舉
public class SingletonTest08 {
public static void main(String[] args) {
Singleton instance = Singleton.INSTANCE;
Singleton instance2 = Singleton.INSTANCE;
System.out.println(instance == instance2);//true
System.out.println(instance.hashCode());
System.out.println(instance2.hashCode());
instance.sayOK();
}
}
//使用枚舉,可以實作單例, 推薦
enum Singleton {
INSTANCE; //屬性
public void sayOK() {
System.out.println("ok~");
}
}
優缺點說明:
- 這借助 JDK1.5 中添加的枚舉來實作單例模式。不僅能避免多線程同步問題,而且還能防止反序列化重新建立新的對象。
- 這種方式是 Effective Java 作者 Josh Bloch 提倡的方式
- 結論: 推薦使用
4.單例模式在 JDK 應用的源碼分析
- 我們 JDK 中,java.lang.Runtime 就是經典的單例模式(餓漢式)
- 代碼分析+Debug 源碼+代碼說明
5. 單例模式注意事項和細節說明
- 單例模式保證了 系統記憶體中該類隻存在一個對象,節省了系統資源,對于一些需要頻繁建立銷毀的對象,使用單例模式可以提高系統性能
- 當想執行個體化一個單例類的時候,必須要記住使用相應的擷取對象的方法,而不是使用 new
- 單例模式 使用的場景:需要 頻繁的進行建立和銷毀的對象、建立對象時耗時過多或耗費資源過多(即:重量級對象),但又經常用到的對象、 工具類對象、頻繁通路資料庫或檔案的對象(比如 資料源、session 工廠等)