大話設計模式之___單例模式
- 什么是單例模式
- 單例模式有哪些(會順帶講些懶漢式的性能優化)
- 懶漢式與餓漢式的區別
什么是單例模式
首先說句題外話,單例模式在很多的開源框架和項目都隨處可見,所以單例模式的重要不言而喻,在一些稍微大點的公司設計模式肯定是會在面試中會問到的,單例模式的命中率不亞于工廠模式等設計模式(在之后的會陸續補上其它項目中經常用到的設計模式)
進入正題,什么是單例模式,通俗的說就是:在整個對象中,單例類只能有一個實例,單例類必須自己創建自己的唯一實例單例類必須給其它對象提供這一實例
小插曲
記得筆者小的時候年輕氣盛,參加鵝廠的春招面試時,面試官大大就問了筆筆一個單例模式的問題,年少無知的我被虐的連媽都不認得。面試官讓我”說說單例模式“?我孩子般的回答說(單例模式就是在整個實例化對象中只有一個唯一實例),然后就沒然后了。。。。。。
其實更全面的回答是說:1. 唯一的實例 2. 給外部的調用者提供單一的入口(這個只針對什么是單例模式,撇開單例模式的種類來說的,在下文中會對單例模式的種類做一個歸納總結)
單例模式有哪些
- 懶漢式
- 餓漢式
總得來說單例模式就分這兩類,懶漢和餓漢,接下對懶漢和餓漢做介紹
懶漢式
摘自維基百科的解釋:懶漢式單例,在第一次調用的時候就實例化自己;懶漢式的優化和問題的癥結都是圍繞這個第一次調用;第一次調用的言外之意,可以理解為如果我還沒調用就創建了實例那么這個就是餓漢式了,講到這恭喜你已經對單例模式掌握百分之60了
下面是我自己對懶漢式的理解,主要是方便記憶,懶漢式:因為我很懶,所以你要想使用我那么你得調用我一次把我激活了我才給你干活
懶漢式單例模式一般寫法
public class Singleton{
private Singleton(){}
private static Singleton single = null;
//靜態工廠方法
public static Singleton getInstance(){
if(single == null){
single = new Singelton();
}
return single;
}
}
PS:上面的方法是線程不安全的,下面給出線程安全的優化方案,想了解靜態工廠方法點它
1. 在getInstance方法上加上同步鎖
public static synchronized Singleton getInstance(){
if(single == null){
single = new Singleton();
}
return single;
}
2. 雙重檢查鎖定
public static Singleton getInstance(){
if(single == null){
synchronized(Singleton.class){
if(single == null){
single = new Singleton();
}
}
}
return single;
}
3. 靜態內部類
public class Singleton{
private static class LazyHolder{
private static final Singleton INSTANCE = new Singleton();
}
private Singleton(){}
public static final Singleton getInstance(){
return LazyHolder.INSTANCE;
}
}
//ps:這種比上面的1,2都好一些,即實現了線程安全,又避免了同步鎖帶來的性能影響
餓漢式
在類初始化的回收,已經自行實例化;它天生就是線程安全的
public class Singleton{
private Singleton(){}
private static final Singleton single = new Singleton();
public static Single getInstance(){
reutrn single;
}
}
//ps:餓漢式在類創建的同時,已經創建好對象,以后不再改變,所以天生就是線程安全的
懶漢式與餓漢式區別
餓漢就是類一旦加載,就把單例初始化完成,保證getInstance的時候,單例對象的實例化已經存在;
而懶漢式比較懶,只有當調用getInstance的時候,才會去初始化這個單例
- 線程安全
餓漢式天生就是線程安全的,可以直接用于多線程;
懶漢式本身是非線程安全的,為了實現線程安全有上面的1、2、3三種方式,這三種方式在資源和性能上有寫區別
- 資源加載和性能
餓漢式在類創建的同時就實例化一個靜態對象,不管之后會不會使用這個單例,都會占據一定的內存,但是相應的,
在第一次調用時速度也會更快,因為資源已經初始化完成。而懶漢式顧名思義,會延遲加載,在第一次使用該單例的會后才會實例化對象出來
第一次調用時要做初始化,如果要做的工作比較多,性能上會有延遲,之后就和餓漢式一樣針對懶漢式1、2、3三種實現的區別
- 第1種:在方法上調用了同步鎖,雖讓線程安全了,但是每次都要同步,會影響性能,畢竟99%的情況下是不需要同步的
- 第2種:在getInstance中做了兩次null檢查,確保了只有第一次調用單例的時候才會去同步,這樣既實現了線程安全,同時避免了每次都同步的性能損耗
- 第3種:利用classLoader機制(類加載機制)來保證初始化instance時既能只有一個線程,所以也是線程安全的,同時沒有性能損耗,推薦這種