博客同步
單例模式作為常用的設(shè)計(jì)模式之一糕再,無論是在各種第三方庫還是在我們?nèi)粘i_發(fā)中都非常常見霞扬,這里將介紹單例模式七種實(shí)現(xiàn)方式。
前提:jvm類加載
class 加載流程: 加載—–驗(yàn)證—–準(zhǔn)備—–解析—–初始化
在class文件中java編譯器會(huì)生成一個(gè)<clinit>()方法骤肛,在初始化階段jvm會(huì)調(diào)用它昌粤,該方法包含了對(duì)該類所有的靜態(tài)變量的賦值和靜態(tài)代碼塊執(zhí)行操作,并且 jvm保證了<clinit>()方法在多線程的執(zhí)行環(huán)境下安全缴挖,因此單例設(shè)計(jì)模式模式中我們使用靜態(tài)變量來存儲(chǔ)實(shí)例的引用。
一焚辅、單例模式之餓漢式
/**
* 餓漢式
*/
public class SingletonDemo {
//當(dāng)類加載初始化后映屋,就已經(jīng)完成了實(shí)例的創(chuàng)建
private static SingletonDemo instance = new SingletonDemo();
/**
* 不讓外部 new
*/
private SingletonDemo() {
}
public static SingletonDemo getInstance() {
return instance;
}
}
餓漢式分析
餓漢式的關(guān)鍵使用了靜態(tài)變量并且在類加載初始化后就完成了實(shí)例的創(chuàng)建,從而保證了多線程環(huán)境下實(shí)例的一致性同蜻,但是它不支持懶加載棚点,當(dāng)然如果過該類比較輕的話還是可以接受的。
二湾蔓、單例模式之懶漢式
/**
* 懶漢式
*/
public class SingletonDemo {
//僅僅定義靜態(tài)變量
private static SingletonDemo instance = null;
/**
* 不讓外部 new
*/
private SingletonDemo() {
}
public static SingletonDemo getInstance() {
return instance!=null?instance:new SingletonDemo();
}
}
懶漢式分析
懶漢式僅僅在在使用實(shí)例的時(shí)候才去創(chuàng)建實(shí)例的對(duì)象瘫析,在多線程環(huán)境下,實(shí)例可能會(huì)創(chuàng)建多次默责,線程不安全贬循,但是它支持懶加載。
三桃序、線程安全懶漢式
/**
* 線程安全懶漢式
*/
public class SingletonDemo {
//僅僅定義靜態(tài)變量
private static SingletonDemo instance = null;
/**
* 不讓外部 new
*/
private SingletonDemo() {
}
//使用synchronized 同步關(guān)鍵字杖虾,保證該方法多線程下只能單個(gè)線程使用
public synchronized static SingletonDemo getInstance() {
return instance!=null?instance:new SingletonDemo();
}
}
線程安全懶漢式分析
通過synchronized 關(guān)鍵字修飾 getInstance方法實(shí)現(xiàn)線程安全,但是同一時(shí)刻只能有一個(gè)線程訪問媒熊,性能較低奇适。
四坟比、單例模式之雙重檢驗(yàn)
/**
* 雙重檢驗(yàn)?zāi)J? */
public class SingletonDemo {
//僅僅定義靜態(tài)變量
private static SingletonDemo instance = null;
/**
* 不讓外部 new
*/
private SingletonDemo() {
}
public static SingletonDemo getInstance() {
if (instance==null){
//同步代碼塊
synchronized (SingletonDemo.class){
if (instance == null) {
instance = new SingletonDemo();
}
}
}
return instance;
}
}
雙重檢驗(yàn)分析
這種模式可能會(huì)引起空指針異常,new 一個(gè)對(duì)象需要經(jīng)歷:分配內(nèi)存空間-初始化-引用賦值(指向?qū)ο蟮牡刂罚┻^程,而且java中存在cpu指令重排序嚷往,因此在多線程環(huán)境下線程一執(zhí)行 可能執(zhí)行new對(duì)象:分配內(nèi)存空間-引用賦值-初始化葛账,當(dāng)執(zhí)行到引用賦值時(shí),線程二會(huì)看到instance!=null皮仁,但這時(shí)籍琳,對(duì)象并未完成初始化,對(duì)象是空的魂贬,直接使用會(huì)空指針異常巩割。因此這種方式也是線程不安全的。
volatile雙重檢驗(yàn)
/**
* volatile雙重檢驗(yàn)?zāi)J? */
public class SingletonDemo {
//僅僅定義靜態(tài)變量
private volatile static SingletonDemo instance = null;
/**
* 不讓外部 new
*/
private SingletonDemo() {
}
public static SingletonDemo getInstance() {
if (instance==null){
//同步代碼塊
synchronized (SingletonDemo.class){
if (instance == null) {
instance = new SingletonDemo();
}
}
}
return instance;
}
}
volatile雙重檢驗(yàn)分析
volatile 在這的作用就是禁止指令重排序付燥,從而使對(duì)象的創(chuàng)建嚴(yán)格按照分配內(nèi)存空間-初始化-引用賦值(指向?qū)ο蟮牡刂罚┻^程宣谈,實(shí)現(xiàn)了線程安全。
靜態(tài)內(nèi)部類模式
/**
* 靜態(tài)內(nèi)部類模式
*/
public class SingletonDemo {
/**
* 不讓外部 new
*/
private SingletonDemo() {
}
public static SingletonDemo getInstance() {
return Holder.instance;
}
//使用靜態(tài)內(nèi)部類來初始化instance
private static class Holder{
//當(dāng)類加載初始化后键科,就已經(jīng)完成了實(shí)例的創(chuàng)建
private static SingletonDemo instance = new SingletonDemo();
}
}
靜態(tài)內(nèi)部類模式分析
靜態(tài)內(nèi)部類模式歸根到底是<clinit>()同步方法帶來的結(jié)果闻丑,這種模式實(shí)現(xiàn)了線程安全和懶加載,被公認(rèn)為最好的單例模式之一勋颖。
枚舉模式
/**
* 枚舉模式
*/
public class SingletonDemo {
/**
* 不讓外部 new
*/
private SingletonDemo() {
}
public static SingletonDemo getInstance() {
return Holder.INSTANCE.instance;
}
//使用枚舉
private enum Holder{
INSTANCE;
private SingletonDemo instance;
Holder(){
instance = new SingletonDemo();
}
}
}
枚舉模式分析
主要利用了enum的特性嗦嗡,保證線程安全。
總結(jié)
各種模式各有有缺點(diǎn)饭玲,但是對(duì)懶加載和高性能有要求的侥祭,還是用靜態(tài)內(nèi)部類類模式、volatile雙重檢驗(yàn)?zāi)J角牙濉⒚杜e模式矮冬,既能滿足線程安全,還能滿足性能要求次哈。