參考:http://blog.csdn.net/jason0539/article/details/23297037/?reload
單例模式有以下特點:
- 單例類只能有一個實例
- 單例類必須自己創(chuàng)建自己的唯一實例
- 單例類必須給所有其他對象提供這一實例
這里介紹三種寫法:懶漢式落追、餓漢式患民、登記式
事實上,通過Java反射機制是能夠?qū)嵗瘶嬙旆椒閜rivate的類的轴猎,那基本上會使所有的Java單例實現(xiàn)失效
1.懶漢式單例
- 構造方法私有化
- 考慮線程安全問題
示例1:同步方法
public class Test {
private static Test single;
private Test(){}
public static synchronized Test getInstance() {
if (single == null) {
single = new Test();
}
return single;
}
}
示例2:雙重檢查鎖定
public static Test getInstance() {
if (single == null) {
synchronized (Test.class) {
if (single == null) {
single = new Test();
}
}
}
return single;
}
示例3:靜態(tài)內(nèi)部類
public class Test {
private static class LazyHolder{
private static final Test INSTANCE = new Test();
}
private Test(){}
public static final Test getInstance() {
return LazyHolder.INSTANCE;
}
}
總結:示例3比1,2都好一些,既實現(xiàn)了線程安全,又避免了同步帶來的性能影響
2.餓漢式單例
示例:
public class Test {
private static final Test single = new Test();
private Test(){}
public static Test getInstance() {
return single;
}
}
總結:餓漢式在類創(chuàng)建的同時就已經(jīng)創(chuàng)建好一個靜態(tài)的對象供系統(tǒng)使用,以后不再改變.所以天生就是安全的
3.登記式單例
用的比較少,內(nèi)部實現(xiàn)其實還是用的餓漢式單例.
登記式單例實際上維護了一組單例類的實例纯路,將這些實例存放在一個Map(登記蓖鹈椤)中,對于已經(jīng)登記過的實例爱咬,則從Map直接返回尺借,對于沒有登記的,則先登記精拟,然后返回燎斩。
4.總結(餓漢式和懶漢式的區(qū)別)
1.線程安全:
- 餓漢式天生就是線程安全,可直接用于多線程
- 懶漢式本身是非線程安全的,但是有安全的寫法.上面介紹的幾種寫法在資源加載和性能方面有些區(qū)別
2.資源加載和性能
餓漢式在類創(chuàng)建的同時就實例化一個靜態(tài)對象出來,不管之后會不會使用這個單例蜂绎,都會占據(jù)一定的內(nèi)存栅表,但是相應的,在第一次調(diào)用時速度也會更快师枣,因為其資源已經(jīng)初始化完成怪瓶,
而懶漢式顧名思義,會延遲加載践美,在第一次使用該單例的時候才會實例化對象出來洗贰,第一次調(diào)用時要做初始化,如果要做的工作比較多陨倡,性能上會有些延遲敛滋,之后就和餓漢式一樣了。
至于1兴革、2绎晃、3這三種實現(xiàn)又有些區(qū)別,
第1種杂曲,在方法調(diào)用上加了同步箕昭,雖然線程安全了,但是每次都要同步解阅,會影響性能落竹,畢竟99%的情況下是不需要同步的,
第2種货抄,在getInstance中做了兩次null檢查述召,確保了只有第一次調(diào)用單例的時候才會做同步朱转,這樣也是線程安全的,同時避免了每次都同步的性能損耗
第3種积暖,利用了classloader的機制來保證初始化instance時只有一個線程藤为,所以也是線程安全的,同時沒有性能損耗夺刑,所以一般我傾向于使用這一種缅疟。
5.拓展
什么是線程安全?
如果你的代碼所在的進程中有多個線程在同時運行遍愿,而這些線程可能會同時運行這段代碼存淫。如果每次運行結果和單線程運行的結果是一樣的,而且其他的變量的值也和預期的是一樣的沼填,就是線程安全的桅咆。
或者說:一個類或者程序所提供的接口對于線程來說是原子操作,或者多個線程之間的切換不會導致該接口的執(zhí)行結果存在二義性,也就是說我們不用考慮同步的問題坞笙,那就是線程安全的岩饼。