驀然回頭-單例模式篇章一
單例模式整理
敲了多年代碼後,回頭來看會別有一番滋味在心頭。。
概念
單例模式是為了保證在一個jvm環境下,一個類僅有一個對象。
代碼中常見的懶漢式。餓漢式,這些實現方式可以通過代碼的設計來強制保證的單例,也可以通過文檔,通過編碼約束,可以認為是一個類僅有一個對象。
代碼場景
項目中被封裝到底層的連接功能的類,數據庫連接,socket連接,具有配置功能的類,工具類,輔助系統類,會需要使用單例模式。這些類的特點:是創建和銷毀需要消耗大量的系統資源,或者不需要創建多個對象。
實戰
單例1,餓漢式
/**
* 最常見的單例,有兩個必不可少的東西
* 1.私有化類的所有構造函數,以阻止其他代碼在該類的外界去創建對象
* 2.提供獲取對象的靜態方法,以返回類唯一的對象引用
*
* @author sunyang
* @date 2018/11/6 18:44
*/
//單例1,通過類的靜態變量來持有一個該類的對象的引用,同時使用final關鍵字來阻止被再次賦值
public class Singleton1 {
private final static Singleton1 INSTANCE = new Singleton1();
public Singleton1() {
}
public static Singleton1 getINSTANCE() {
return INSTANCE;
}
}
單例2,餓漢式
/**
* 單例2,是使用靜態變量維護該類的引用,但是要將對象創建放在靜態代碼塊中
*
* @author sunyang
* @date 2018/11/6 18:49
*/
public class Singleton2 {
private static final Singleton2 INSTANCE;
static {
INSTANCE = new Singleton2();
}
public Singleton2() {
}
public static Singleton2 getInstance(){
return INSTANCE;
}
}
單例3,懶漢式
/**
* 單例3,使用靜態變量維持類的對象引用,在獲取對象的方法中對象進行判斷和創建
*
* @author sunyang
* @date 2018/11/6 18:59
*/
public class Singleton3 {
public static Singleton3 instance;
private Singleton3(){
}
private static Singleton3 getInstance(){
if (instance == null){
instance = new Singleton3();
}
return instance;
}
}
分析
單例1和單例2,將對象創建放在類的初始化階段,單例3則是將對象的創建放在類的使用階段。
所以,單例1和單例2稱為餓漢式,單例3稱為懶漢式。優缺點:
餓漢式的優點是簡單易懂,缺點是沒有達到懶加載的效果。如果整個生命周期
中自始至終未使用過這個實例,就會比較浪費連接資源和內存。
懶漢式並不復雜,可以起到懶加載的效果。於是,實際開發中更願意使用懶漢式,因為節省內存,懶加載。
聯想
類的生命周期:
1.類的加載:
將類的字節碼文件.class
從硬盤載入方法區的過程
2.類的連接:
該過程由三個部分組成:驗證、準備和解析
3.類的初始化:
將靜態變量賦值,執行的順序就是,
父類靜態變量->靜態代碼塊->子類靜態變量->子類靜態代碼塊
餓漢式的對象創建在這個階段
4.類的使用:
類的實例化,懶漢式的對象創建處於這個階段,new關鍵字可以觸發該生命周期
5.類卸載
驗證
/**
* 如果驗證?
* 根據類的五個生命周期階段,我們只需要驗證在創建對象之前的
* 那些操作能夠觸發類的初始化就行。
*
* @author sunyang
* @date 2018/11/8 11:13
*/
public class ValidSingleton1 {
/**
* 首先在構造方法裏添加打印語句,打印"init"
* 再添加靜態方法和一個靜態變量
*
*/
private static final ValidSingleton1 INSTANCE = new ValidSingleton1();
//打印init
public ValidSingleton1() {
System.out.println("init");
}
public static ValidSingleton1 getINSTANCE() {
return INSTANCE;
}
//靜態方法
public static final void otherMethod(){
}
//靜態變量
public static final int staticField = 0;
}
開始驗證
/**
* @author sunyang
* @date 2018/11/8 11:26
*/
public class ValidSingletonDemo {
public static void main(String[] args) {
System.out.println("---------------------start");
/*1.驗證單例1,只聲明,不會觸發類的初始化階段
ValidSingleton1 validSingleton1 = null;
if (null == validSingleton1){
System.out.println("singleton1 is null");
}*/
/*//2.調用類的靜態變量
System.out.println(ValidSingleton1.staticField);*/
//3.調用類的靜態方法
// ValidSingleton1.otherMethod();
//4.初始化兩次
ValidSingleton1 validSingleton1 = new ValidSingleton1();
//5.直接調用
// ValidSingleton1.getINSTANCE();
System.out.println("---------------------end");
/**
* -------start-------
* singleton1 is null
* -------end---------
*
* ---------------------start
* 0
* ---------------------end
*
* ---------------------start
* init
* ---------------------end
*
* ---------------------start
* init
* init
* ---------------------end
*
*
* ---------------------start
* init
* ---------------------end
*/
}
}
從輸出上看,調用類的靜態方法以下,會觸發類的初始化階段。
發現餓漢式,是不會產生線程安全的問題;
在設計上,懶漢式要優於餓漢式
關聯線程安全
在多線程下,懶漢式會有一定修改。當兩個線程在if(null == instance)語句阻塞的時候,可能由兩個線程進入創建實例,從而返回了兩個對象。對此,我們可以加鎖,保證僅有一個線程處於getInstance()方法中,從而保證了線程一致性。多線程下的單例
/**
* @author sunyang
* @date 2018/11/8 12:18
*/
public class Singleton4 {
private static Singleton4 instance;
private Singleton4(){
}
//需要加上synchronized 同步
public static synchronized Singleton4 getInstance(){
if (instance == null){
instance = new Singleton4();
}
return instance;
}
}
驀然回頭-單例模式篇章一