本文學習一個Java單例模式。
單例模式
單例,顧名思義,就是隻存在一個實例。或許,你也會疑問?爲什麼會使用到單例模式呢?這是因爲,很多情況下,我們需要一個實例,比如線程池、緩存、驅動等,如果存在多個實例,那將會導致混亂。
首先我們複習下構造函數,
構造函數
構造函數的用處是實例化對象,
我們的用法通常是這樣,
public class ClassA {
public ClassA() {
}
}
用到該類的時候,我們可以這樣實例化,
ClassA a = new ClassA();
有兩個地方需要我們注意,
1. 構造函數名與類名一致
2. 權限修飾符爲public
關於上述兩條,第1條,類名一致是Java規定的,那有沒有想過第2條,權限修飾符必須是public麼?答案是不是,我們可以將其修改爲private,但是這樣就會帶來另一個問題,由於是private,在其他類中,無法調用該函數,所以就無法實例化,那麼如何實例化呢?
既然構造函數無法訪問,那麼我們能不能通過其他函數來得到該類的實例呢?答案是可以的。
public class ClassA {
private ClassA() {
}
public static ClassA getClassA(){
return new ClassA();
}
}
上面代碼中,提供了一個getClassA方法,該方法的返回類型是ClassA,函數的主體是返回一個ClassA的實例,注意到該函數是靜態函數(static),爲什麼是靜態函數呢?靜態函數與非靜態函數的最主要區別在於,靜態函數可以直接通過類名來調用,而非靜態函數必須通過對象實例化來引用。
然後,我們可以這樣來獲取ClassA實例,
ClassA a = ClassA.getClassA();
那麼,我們將其修改成下面代碼,就成爲了單例模式,
單例1
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
首先聲明一個實例uniqueInstance,在getInstance方法中,判斷uniqueInstance是否爲null,若爲null,則實例化並賦值,若不爲null,則直接返回。該方法保證了uniqueInstance爲唯一的實例。
然而,在多線程下,該方法卻會出現問題,因爲,如果線程1和線程2同時調用該函數,將會出現同時修改現象。在操作系統中,這屬於多線程同步問題,我們可以將其獲取實例方法定義爲同步方法,就可以避免該現象。
單例2
public static synchronized Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
synchronized 關鍵字的意思是,同步。意味着,在同一時刻,只能有一個對象調用該方法,其他調用方法處於等待狀態,直到調用結束,其他調用方法纔會一一執行(同步)。這樣就保證了不會同時修改對象。
然而,該方法也存在一個缺點,那就是如果很多個地方都需要調用getInstance方法的話,那樣JVM的壓力很大,因爲它要保證這麼多個調用都是同步調用(一個一個順序執行),同時,也會導致多個線程在getInstance方法上等待,這樣顯然帶來了較大的性能影響。我們可以使用方法3和方法4來改進.
單例3
我們將代碼修改成下面這樣,
public class Singleton {
private static Singleton uniqueInstance = new Singleton();
private Singleton() {
}
public static synchronized Singleton getInstance() {
return uniqueInstance;
}
}
和單例2最主要的區別,在於uniqueInstance唯一實例,是在加載類的時候創建的(static關鍵字造成的),這樣就可以保證,在所有調用getInstance之前,uniqueInstance已被初始化,該方法也不會給JVM帶來額外的負擔。
單例4
我們將代碼修改爲,
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getInstance() {
if (uniqueInstance == null) {
synchronized (Singleton.class) {//同步代碼塊
if (uniqueInstance == null)
uniqueInstance = new Singleton();
}//同步代碼塊結束
}
return uniqueInstance;
}
}
與單例2的區別在於,synchronized 關鍵字不是作用在getInstance方法上,這樣調用getInstance方法並不會給JVM帶來負擔(JVM並不需要對線程調用進行調整,進行順序執行)。
當多個線程調用getInstance方法時,該方法並不會阻塞,而是會在代碼塊上進行阻塞。在同步代碼塊內,爲什麼要再次檢查是否爲空呢?設想以下情形,10個線程在實例爲null的情況下同時調用getInstance方法,那麼只會有一個線程(姑且叫做線程A)能得到Singleton的鎖,其他線程都會在同步代碼塊上阻塞,當線程A執行完畢後,此時uniqueInstance應該不爲null,但是也不能確保其一定不是null,如果線程A沒有正常執行完畢,就被中斷了呢?所以我們在同步代碼塊裏需要再次判斷一下uniqueInstance 是否爲null,若不爲null,直接返回就是,若爲null,則要重新實例化。
好了,就寫到這裏,希望您能有所收穫,有啥問題,可以私信或回覆。