有一些對象我們隻需要使用一個,例如線程池,緩存,對話框,日志對象等等。如果這些對象有多個執行個體,就會導緻許多問題産生,像程式的行為異常,記憶體溢出,或者是不一緻的結果。
使用全局變量可以達到效果,但同時,全局變量在程式一開始就被建立,如果這是一個占資源大的對象,而在這次執行中程式沒有使用到該變量,這就形成了資源浪費。
定義: 單例模式確定類隻有一個執行個體,并提供一個全局通路點。
經典的單例模式實作
public class Singleton {
private static Singleton uniqueInstance;
private Singleton(){}
public static Singleton getInstance(){
if (uniqueInstance == null){
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
//其他方法
}
上面就是一個經典的單例模式,私有構造器,共有建立方法,當類變量為空時,我們建立一個執行個體并傳回它,我們稱這種方式為延遲執行個體化,也就是”餓漢模式“。下面再看一個例子
原始代碼
public class ChocolateBoiler {
private boolean empty;
private boolean boiled;
public ChocolateBoiler() {
empty = true;
boiled = false;
}
public void fill(){
if (isEmpty()){
empty = false;
boiled = false;
}
}
public void drain(){
if(!isEmpty()&&!isBoiled()){
empty = true;
}
}
public void boil(){
if(!isEmpty()&&!isBoiled()){
boiled = true;
}
}
public boolean isEmpty(){
return empty;
}
public boolean isBoiled(){
return boiled;
}
}
為了使得不出現不一緻的情況,我們決定使用單例模式,如下
懶漢模式
public class ChocolateBoiler {
private boolean empty;
private boolean boiled;
private static ChocolateBoiler instance;
private ChocolateBoiler() {
empty = true;
boiled = false;
}
public static ChocolateBoiler getInstance(){
if(instance == null){
instance = new ChocolateBoiler();
}
return instance;
}
//其他方法
}
出現問題了: 我們的fill()方法竟然允許在boil()時候繼續添加原料,這不符合我們的邏輯。
經過檢查,我們發現這是由于有兩個線程同時去擷取這個執行個體造成的錯誤,解決方法如下
加鎖
public class ChocolateBoiler {
private boolean empty;
private boolean boiled;
private static ChocolateBoiler instance;
private ChocolateBoiler() {
empty = true;
boiled = false;
}
public static synchronized ChocolateBoiler getInstance(){
if(instance == null){
instance = new ChocolateBoiler();
}
return instance;
}
//其他方法
}
在方法上加鎖能確定同時隻有一個線程能夠拿到該執行個體。但是,使用synchronized會降低性能,而我們隻有第一次執行該方法時才需要同步,之後每次調用該方法都會造成對性能的浪費。
“餓漢模式”
public class ChocolateBoiler {
private boolean empty;
private boolean boiled;
private static ChocolateBoiler instance = new ChocolateBoiler();
private ChocolateBoiler() {
empty = true;
boiled = false;
}
public static synchronized ChocolateBoiler getInstance(){
return instance;
}
}
雙重加鎖檢查
public class ChocolateBoiler {
private boolean empty;
private boolean boiled;
private volatile static ChocolateBoiler instance;
private ChocolateBoiler() {
empty = true;
boiled = false;
}
public static ChocolateBoiler getInstance(){
if(instance == null){
synchronized (ChocolateBoiler.class){
if(instance == null){
instance = new ChocolateBoiler();
}
}
}
return instance;
}
}
上例使用了雙重檢查,首先檢查是否建立了執行個體,隻有未建立的時候才會進入同步塊。這樣就大大降低了性能消耗