Java設計模式1:單例模式
咳咳,想系統的整理一下知識想了好久了,畢竟工作了快半年了,業務程式碼感覺已經寫得差不多了,明顯感覺到又到了再夯實一遍基礎的時候了,畢竟基礎打得好後面才能得心應手,事半功倍。所以就從設計模式這裡開始看吧。
設計模式感覺在寫程式碼的時候也是挺重要的,確實有些時候就是不知道該如何設計自己的程式碼,所以這次就從這裡入手啦。話雖如此我也不打算全寫,就挑著常用的來寫吧,感覺全都寫了還是有點多的。。。
囉嗦了一大片,現在就從最常用的單例模式開始寫吧。
單例模式是什麼
簡單來說單例模式就是一種類的寫法,它保證了你所寫的類最多隻會被例項化一個物件。
使用場景
單例模式只允許建立一個物件,因此節省記憶體,加快物件訪問速度,因此物件需要被公用的場合適合使用,如多個模組使用同一個資料來源連線物件等等。如:
1.需要頻繁例項化然後銷燬的物件。
2.建立物件時耗時過多或者耗資源過多,但又經常用到的物件。
3.有狀態的工具類物件。
4.頻繁訪問資料庫或檔案的物件。
以下都是單例模式的經典使用場景:
1.資源共享的情況下,避免由於資源操作時導致的效能或損耗等。如上述中的日誌檔案,應用配置。
2.控制資源的情況下,方便資源之間的互相通訊。如執行緒池等。
實現方法
首先說一下所有寫法的共通之處,也是單例模式要想保證唯一物件並正常使用的必要寫法:
1.構造器私有
2.自己建立自己的例項化物件
3.提供給外界獲得此物件的方法
通過構造器私有,我們的類將不能夠被外界例項化,同時外界只能通過本類提供的方法來獲取由本類已經建立好的物件。
首先是最常見的兩種方法,懶漢式和餓漢式:
首先是餓漢式:
/** 餓漢式*/
public class Singleton {
private static Singleton singleton = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return singleton;
}
public void sayHello(){
System.out.println("hello world !");
}
}
接下來是懶漢式:
/** 懶漢式*/
public class Singleton{
private static Singleton singleton = null;
private Singleton(){}
public static Singleton getInstance() {
if (singleton==null){
singleton=new Singleton();
}
return singleton;
}
}
這兩個我們對比來看,主要區別就在於new Singleton()這一步執行的時機,餓漢式在初始化系統變數的時候就進行了new這一步動作,而當需要的時候直接返回已有的物件;反過來看懶漢式則並沒有在初始化系統變數的時候建立物件,而是在呼叫的時候進行判斷,如果還沒有物件,就new一個出來,如果有就直接返回去,因此懶漢式總是在第一次呼叫的時候建立物件,以後一直返回這個物件。
這兩種方法各有各的側重點,餓漢式由於是在載入類的時候進行的物件建立,但是該物件可能在接下來的一段時間內並沒有使用,所以會造成資源的浪費。而懶漢式在使用的時候進行建立,也就保證了不會白白浪費掉,但是建立物件畢竟會花費時間,所以又回到了計算機的一個永恆不變的話題,是時間換空間,還是空間換時間?
除了對於空間和時間的傾向性不同,還有一個問題需要我們考慮,那就是執行緒安全的問題。
對於餓漢式來說,由於虛擬機器在載入類的時候會自動保證只有一個執行緒來處理,所以餓漢式是執行緒安全的;但是對於懶漢式來說,有可能出現在呼叫時,當第一個執行緒判斷為空,進入了建立物件的步驟,這時候執行緒切換,第二個執行緒也執行到這裡,由於上一個執行緒還沒能創建出來物件,所以第二個執行緒也判斷為空,進入了建立執行緒的操作,這樣便會導致我們的單例不再”單例“。
既然知道了癥結在哪裡,那我們就對症下藥就好了,我們知道通過synchronized關鍵字可以進行執行緒的同步,所以我們在可能產生問題的部分,也就是為空判斷上加上執行緒同步:
/**
* 懶漢式(加執行緒同步)
*/
public class Singleton {
private static Singleton singleton = null;
private Singleton() {}
public static Singleton getInstance() {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
}
從而保證了懶漢式的單一例項。
但是問題又來了,眾所周知的執行緒同步會導致效率低下,那麼有沒有辦法提高一下效率呢?通過研究之前的程式碼我們發現,所有想要使用該類的物件的地方都要通過getInstance()方法,而每次執行該方法都會直接進入synchronized片段,所以我們可以在該片段之外再加上一層判斷,判斷該類的例項是否已經初始化成功
/**
* 懶漢式(雙重加鎖執行緒同步)
*/
public class Singleton {
private static Singleton singleton = null;
private Singleton() {}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
return singleton;
}
}
這樣在懶載入的時候我們終於能夠保證執行緒同步了,真的不容易。。。。
好了,上面介紹完麻煩的方法,我們來介紹一個非常巧妙的方法
/** 內部類方式*/
public class Singleton {
private Singleton(){}
private static class SingletonHolder {
private static final Singleton instance=new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.instance;
}
public void sayHello(){
System.out.println("hello world !");
}
}
我們可以看到在我們要實現單例的類內部實現了一個內部類,然後通過內部類來持有我們的例項物件。這種方法的巧妙之處就在於將餓漢式和懶漢式的優點都結合起來了,由於我們的內部類是在呼叫的時候才進行載入的,所以擁有懶漢式節省資源的好處,同時由於我們的例項物件是在載入內部類的過程中例項化的,又會有虛擬機器級別的執行緒安全保證,可以說是一舉兩得了。
除了這種方法以外,由於Java在jdk1.5之後添加了列舉型別,可以說列舉型別的特性和單例模式有異曲同工之妙了,我們可以通過構建一個只有一個例項的列舉型別來實現單例模式:
/** 內部類方式*/
public enum Singleton {
INSTANCE;
public void sayHello(){
System.out.println("hello world !");
}
}
只需要在列舉的時候只列舉一個例項,單例模式就自動完成了!簡單到爆炸QWQ