兩種單例常見的實現方式:
1:懶漢的設計模式,在第一次呼叫的時候才完成相關的初始化操作
懶漢式是典型的時間換空間,就是每次獲取例項都會進行判斷,看是否需要建立例項,浪費判斷的時間。當然,如果一直沒有人使用的話,那就不會建立例項,則節約記憶體空間
package com.xiaohao.test;
/**
* 使用懶漢模式建立一個單例模式
* @author lenovo
*
*/
public class Singleton {
private static Singleton instance=null;
/**
* 返回或者建立相關的單例例項
* @return
*/
public static synchronized Singleton getInstance(){//使用同步方法保證執行緒安全
if(instance==null){
instance=new Singleton();
}
return instance;
}
/**
* 一個私有的構造方法,使外部的物件不能new相關的例項,這個尤其要注意,一定要提供預設的私有化的方法去覆蓋預設的構造方法
* 否則的話,如果使用者直接去new一個物件的話,就無法保證單例了~~~
*/
private Singleton(){}
}
2:餓漢的設計模式,在初始化類的過程中就會完成相關例項的初始化,一般認為這種方式要更加安全些
餓漢式是典型的空間換時間,當類裝載的時候就會建立類的例項,不管你用不用,先建立出來,然後每次呼叫的時候,就不需要再判斷,節省了執行時間。
package com.xiaohao.test;
/**
* 使用餓漢模式建立一個單例 模式
* @author lenovo
*
*/
public class Singleton {
//這個內部的單例僅僅用於內部訪問,在初始化相關類的時候,就完成了相關單例類的初始化
//同時使用final修飾符修飾,一旦初始化值之後,不允許修改相關的值,這樣也就實現了系統中的唯一
private static final Singleton instance=new Singleton();
/**
* 返回或者建立相關的單例例項
* @return
*/
public static Singleton getInstance(){
return instance;
}
/**
* 一個私有的構造方法,使外部的物件不能new相關的例項,這個尤其要注意,一定要提供預設的私有化的方法去覆蓋預設的構造方法
* 否則的話,如果使用者直接去new一個物件的話,就無法保證單例了~~~
*/
private Singleton(){}
}
此外還有一個比較給力的實現單例的方式,推薦大家使用這種方式:
/**
* 類級的內部類,也就是靜態的成員式內部類,該內部類的例項與外部類的例項
* 沒有繫結關係,而且只有被呼叫到時才會裝載,從而實現了延遲載入。
*/
/**
* 靜態初始化器,由JVM來保證執行緒安全
*/
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
在載入singleton時並不載入它的內部類SingletonHolder,而在呼叫getInstance()時呼叫SingletonHolder時才載入SingletonHolder,從而呼叫singleton的建構函式,例項化singleton,從而達到lazy loading的效果。
3:雙從鎖的設計模式,實現在多執行緒下,相關效能的提高,尤其避免懶漢模式中,每次獲取例項的時候都需要執行緒同步的問題
雙重鎖的主要目的是解決每次獲取例項的時候都需要進行執行緒等待,只有在第一次例項化時,才啟用同步機制,提高了效能。
提示:由於volatile關鍵字可能會遮蔽掉虛擬機器中一些必要的程式碼優化,所以執行效率並不是很高。因此一般建議,沒有特別的需要,不要使用。也就是說,雖然可以使用“雙重檢 查加鎖”機制來實現執行緒安全的單例,但並不建議大量採用,可以根據情況來選用。
package com.xiaohao.test;
/**
* 使用雙重鎖模式建立一個單例模式
* @author lenovo
*
*/
public class Singleton {
private static Singleton instance=null;
/**
* 返回或者建立相關的單例例項
* @return
*/
public static Singleton getInstance(){
if(instance==null)
{
synchronized (Singleton.class){//需要注意的是,這裡鎖住的是類,一旦加上這個類鎖之後,所有和這個類相關的操作(包括這個類的所有屬性和方法)都需要進行相關的等待
if(instance==null){
instance=new Singleton();
}
}
}
return instance;
}
/**
* 一個私有的構造方法,使外部的物件不能new相關的例項,這個尤其要注意,一定要提供預設的私有化的方法去覆蓋預設的構造方法
* 否則的話,如果使用者直接去new一個物件的話,就無法保證單例了~~~
*/
private Singleton(){}
}
}
這個方法表面上看起來很完美,你只需要付出一次同步塊的開銷,但它依然有問題。除非你宣告instance
變數時使用了volatile關鍵字。沒有volatile
修飾符,可能出現Java中的另一個執行緒看到個初始化了一半的instance
的情況,但使用了volatile
變數後,就能保證先行發生關係(happens-before relationship)。對於volatile
變數_instance
,所有的寫(write)都將先行發生於讀(read),在Java 5之前不是這樣,所以在這之前使用雙重檢查鎖有問題。現在,有了先行發生的保障(happens-before guarantee),你可以安全地假設其會工作良好。另外,這不是建立執行緒安全的單例模式的最好方法,你可以使用列舉實現單例模式,這種方法在例項建立時提供了內建的執行緒安全。另一種方法是使用靜態持有者模式(static holder pattern),這種方式就是開始的餓漢模式,所以說餓漢模式相對來說比較安全一些。
package com.xiaohao.test;
/**
* 使用雙重鎖模式建立一個單例模式,雙重鎖模式下的進一步優化
* @author lenovo
*
*/
public class Singleton {
//注意這裡面的volatile 這個關鍵字,它的主要作用是寫(write)優於讀(read),前提是jdk的版本要不低於jdk5.0
private volatile static Singleton instance=null;
/**
* 返回或者建立相關的單例例項
* @return
*/
public static Singleton getInstance(){
if(instance==null)
{
synchronized (Singleton.class){//需要注意的是,這裡鎖住的是類,一旦加上這個類鎖之後,所有和這個類相關的操作(包括這個類的所有屬性和方法)都需要進行相關的等待
if(instance==null){
instance=new Singleton();
}
}
}
return instance;
}
/**
* 一個私有的構造方法,使外部的物件不能new相關的例項,這個尤其要注意,一定要提供預設的私有化的方法去覆蓋預設的構造方法
* 否則的話,如果使用者直接去new一個物件的話,就無法保證單例了~~~
*/
private Singleton(){}
}
4:使用列舉實現單例模式,這種方法在例項建立時提供了內建的執行緒安全
package com.xiaohao.test;
/**
* 使列舉的方法建立一個單例類
* @author lenovo
*
*/
public enum Singleton {
INSTANCE;// 使用列舉類的方法建立一個單例,據說這是目前位置,無論是多執行緒還是,防止反射建立
//相關物件最有用的方法,推薦使用
}
單元素的列舉型別已經成為實現Singleton的最佳方法。用列舉來實現單例非常簡單,只需要編寫一個包含單個元素的列舉型別即可。使用enum關鍵字來實現單例模式的好處是這樣非常簡潔,並且無償地提供了序列化機制,絕對防止多次例項化,即使是在面對複雜的序列化或者反射攻擊的時候。使用列舉來實現單例項控制會更加簡潔,而且無償地提供了序列化機制,並由JVM從根本上提供保障,絕對防止多次例項化,是更簡潔、高效、安全的實現單例的方式。——來自《Effective Java》
***知識天補充(拷貝的)----針對谷歌那個工程師的方法:
這個模式綜合使用了Java的類級內部類和多執行緒預設同步鎖的知識,很巧妙地同時實現了延遲載入和執行緒安全。
1.相應的基礎知識
- 什麼是類級內部類?
簡單點說,類級內部類指的是,有static修飾的成員式內部類。如果沒有static修飾的成員式內部類被稱為物件級內部類。
類級內部類相當於其外部類的static成分,它的物件與外部類物件間不存在依賴關係,因此可直接建立。而物件級內部類的例項,是繫結在外部物件例項中的。
類級內部類中,可以定義靜態的方法。在靜態方法中只能夠引用外部類中的靜態成員方法或者成員變數。
類級內部類相當於其外部類的成員,只有在第一次被使用的時候才被會裝載。
- 多執行緒預設同步鎖的知識
大家都知道,在多執行緒開發中,為了解決併發問題,主要是通過使用synchronized來加互斥鎖進行同步控制。但是在某些情況中,JVM已經隱含地為您執行了同步,這些情況下就不用自己再來進行同步控制了。這些情況包括:
1.由靜態初始化器(在靜態欄位上或static{}塊中的初始化器)初始化資料時
2.訪問final欄位時
3.在建立執行緒之前建立物件時
4.執行緒可以看見它將要處理的物件時
2.解決方案的思路
要想很簡單地實現執行緒安全,可以採用靜態初始化器的方式,它可以由JVM來保證執行緒的安全性。比如前面的餓漢式實現方式。但是這樣一來,不是會浪費一定的空間嗎?因為這種實現方式,會在類裝載的時候就初始化物件,不管你需不需要。
如果現在有一種方法能夠讓類裝載的時候不去初始化物件,那不就解決問題了?一種可行的方式就是採用類級內部類,在這個類級內部類裡面去建立物件例項。這樣一來,只要不使用到這個類級內部類,那就不會建立物件例項,從而同時實現延遲載入和執行緒安全。