一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

單例模式的八種寫(xiě)法

 行者花雕 2021-07-23

單例模式作為日常開(kāi)發(fā)中最常用的設(shè)計(jì)模式之一,是最基礎(chǔ)的設(shè)計(jì)模式,也是最需要熟練掌握的設(shè)計(jì)模式。單例模式的定義是:保證一個(gè)類(lèi)僅有一個(gè)實(shí)例,并提供一個(gè)訪問(wèn)它的全局訪問(wèn)點(diǎn)。那么你知道單例模式有多少種實(shí)現(xiàn)方式嗎?以及每種實(shí)現(xiàn)方式的利弊呢?

  • 餓漢模式

  • 懶漢模式(線程不安全)

  • 懶漢模式(線程安全)

  • 雙重檢查模式(DCL)

  • 靜態(tài)內(nèi)部類(lèi)單例模式

  • 枚舉類(lèi)單例模式

  • 使用容器實(shí)現(xiàn)單例模式

  • CAS實(shí)現(xiàn)單例模式?

餓漢模式

代碼如下:

public class Singleton {   private static Singleton instance = new Singleton();   private Singleton () { } public static Singleton getInstance() {   return instance;   }  
 }

這種方式在類(lèi)加載時(shí)就完成了實(shí)例化,會(huì)影響類(lèi)的加載速度,但獲取對(duì)象的速度快。 這種方式基于類(lèi)加載機(jī)制保證實(shí)例僅有一個(gè),避免了多線程的同步問(wèn)題,是線程安全的。?

懶漢模式(線程不安全)

絕大多數(shù)時(shí)候,類(lèi)加載的時(shí)機(jī)和對(duì)象使用的時(shí)機(jī)都是分開(kāi)的,所以沒(méi)有必要在類(lèi)加載的時(shí)候就去實(shí)例化單例對(duì)象。為了消除單例對(duì)象實(shí)例化對(duì)類(lèi)加載的影響,引入了延遲加載,就有了懶漢模式的實(shí)現(xiàn)方式。代碼如下:

public class Singleton {  private static Singleton instance;  private Singleton () {}     public static Singleton getInstance() { if (instance == null) {instance = new Singleton();  }  return instance;  }  }

懶漢模式聲明了一個(gè)靜態(tài)對(duì)象,在用戶(hù)第一次調(diào)用時(shí)完成實(shí)例化,屬于延遲加載方式。而且這種方式不是線程安全。?

懶漢模式(線程安全)

針對(duì)線程不安全的懶漢模式,對(duì)其中的獲取單例對(duì)象的方法增加同步關(guān)鍵字。代碼如下:

public class Singleton {    private static Singleton instance;    private Singleton () {  }  public static synchronized Singleton getInstance() {    if (instance == null) {    instance = new Singleton();    }    return instance;    }  }

這種寫(xiě)法保證了線程安全,但是每次調(diào)用getInstance方法獲取單例時(shí)都需要進(jìn)行同步,造成不必要的同步開(kāi)銷(xiāo),但實(shí)際上除了第一次實(shí)例化需要同步,其他時(shí)候都是不需要同步的。

雙重檢查模式(DCL)

既然懶漢模式中的實(shí)例化只需要在第一次的時(shí)候保證同步,那何不只在實(shí)例為空的時(shí)候加同步關(guān)鍵字呢。代碼如下:

public class Singleton {    private volatile static Singleton singleton;  // 1  private Singleton () {  }     public static Singleton getInstance() {    if (instance== null) {  // 2  synchronized (Singleton.class) {  // 3  if (instance== null) {  // 4  instance= new Singleton();  // 5  }   }   }   return singleton;  }  }

雙重檢查寫(xiě)法,主要關(guān)注以上代碼中的5點(diǎn):

  1. 聲明單例對(duì)象時(shí)加上volatile關(guān)鍵字,保證多線程的內(nèi)存可見(jiàn)性,也即當(dāng)在一個(gè)線程中單例對(duì)象實(shí)例化完成之后,其他線程也同時(shí)能夠看到。同時(shí),還有更為重要的一點(diǎn),下面會(huì)說(shuō)。

  2. 第一次檢查單例對(duì)象是否為空,判斷是否已經(jīng)完成了實(shí)例化。

  3. 如果第一次檢查發(fā)現(xiàn)單例對(duì)象為空,那么該線程就要對(duì)此單例類(lèi)進(jìn)行加鎖,準(zhǔn)備進(jìn)行實(shí)例化,加鎖是為了保證該線程進(jìn)行實(shí)例化的時(shí)候沒(méi)有其他線程也同時(shí)進(jìn)行實(shí)例化。

  4. 第二次檢查單例對(duì)象是否為空,則是為了避免這種情況:此時(shí)單例對(duì)象為空,兩個(gè)線程,A線程在第2步,B線程在第5步,A線程發(fā)現(xiàn)單例對(duì)象為空,緊接著B(niǎo)線程就完成了實(shí)例化,然后就會(huì)導(dǎo)致A線程又會(huì)走一次第5步的實(shí)例化過(guò)程,即重復(fù)實(shí)例化。那么加上了第二次檢查后,當(dāng)A線程到第4步的時(shí)候就會(huì)發(fā)現(xiàn)單例對(duì)象已經(jīng)實(shí)例化完成,自然不會(huì)到第5步。

  5. 真正的實(shí)例化操作就發(fā)生在第5步,且只發(fā)生一次。

DCL思考

在以上代碼的第一步中,我們提到volatile關(guān)鍵字,volatile關(guān)鍵字除了保證內(nèi)存可見(jiàn)性,還有一點(diǎn)是禁止指令重排序。那么問(wèn)題出在哪里呢?對(duì),第5步。實(shí)際上,實(shí)例化對(duì)象的動(dòng)作并不是一個(gè)原子操作,instance= new Singleton();可以分為以下三步完成:

memory = allocate(); // 5.1:分配對(duì)象的內(nèi)存空間ctorInstance(memory); // 5.2:初始化對(duì)象instance = memory; // 5.3: 設(shè)置instance指向剛分配的內(nèi)存地址

而上面三行代碼,5.2和5.3可能發(fā)生重排序。跟著上面代碼中的第二次檢查的位置進(jìn)行分析。當(dāng)線程B執(zhí)行到5.3之后,5.2之前時(shí),這時(shí)候線程A首次判斷單例對(duì)象是否為空。這時(shí)候當(dāng)然單例對(duì)象是不為空的,但是卻不能使用,因?yàn)閱卫龑?duì)象還沒(méi)有被初始化呢。這既是DCL的缺陷所在,也是為什么要對(duì)單例對(duì)象家volatile關(guān)鍵字的原因。禁止了指令重排序,自然不會(huì)出現(xiàn)線程A拿到一個(gè)不可用的單例對(duì)象。

靜態(tài)內(nèi)部類(lèi)單例模式

public class Singleton { private Singleton() {}public static Singleton getInstance() {  return SingletonHolder.sInstance;  }  private static class SingletonHolder {  private static final Singleton sInstance = new Singleton();  }  }

第一次加載Singleton類(lèi)時(shí)并不會(huì)初始化sInstance,只有第一次調(diào)用getInstance方法時(shí)虛擬機(jī)加載SingletonHolder 并初始化sInstance ,這樣不僅能確保線程安全也能保證Singleton類(lèi)的唯一性,所以推薦使用靜態(tài)內(nèi)部類(lèi)單例模式。

枚舉類(lèi)單例模式

public enum Singleton {   INSTANCE;   public void doSomeThing() {   }  
 }

那這個(gè)單例如何來(lái)填充屬性呢,增加構(gòu)造函數(shù)和屬性即可啦,請(qǐng)看代碼:

public enum Singleton {  INSTANCE("name", 18);private String name;private int age;Singleton(String name, int age) {this.name = name;this.age = age;}   public void doSomeThing() {   }  
 }

默認(rèn)枚舉實(shí)例的創(chuàng)建是線程安全的,并且在任何情況下都是單例,上述講的幾種單例模式實(shí)現(xiàn)中,有一種情況下他們會(huì)重新創(chuàng)建對(duì)象,那就是反序列化,將一個(gè)單例實(shí)例對(duì)象寫(xiě)到磁盤(pán)再讀回來(lái),從而獲得了一個(gè)實(shí)例。反序列化操作提供了readResolve方法,這個(gè)方法可以讓開(kāi)發(fā)人員控制對(duì)象的反序列化。在上述的幾個(gè)方法示例中如果要杜絕單例對(duì)象被反序列化是重新生成對(duì)象,就必須加入如下方法:

private Object readResolve() throws ObjectStreamException{return singleton;}

使用容器實(shí)現(xiàn)單例模式

代碼如下:

public class SingletonManager {   private static Map<String, Object> objMap = new HashMap<String,Object>();  private Singleton() {   }  public static void registerService(String key, Objectinstance) {    if (!objMap.containsKey(key) ) {      objMap.put(key, instance) ;    }  }  public static ObjectgetService(String key) {    return objMap.get(key) ;  }}

在程序的初始化,將多個(gè)單例類(lèi)型注入到一個(gè)統(tǒng)一管理的類(lèi)中,使用時(shí)通過(guò)key來(lái)獲取對(duì)應(yīng)類(lèi)型的對(duì)象,這種方式使得我們可以管理多種類(lèi)型的單例,并且在使用時(shí)可以通過(guò)統(tǒng)一的接口進(jìn)行操作。這種方式是利用了Map的key唯一性來(lái)保證單例。

CAS實(shí)現(xiàn)單例模式

以上實(shí)現(xiàn)主要用到了兩點(diǎn)來(lái)保證單例,一是JVM的類(lèi)加載機(jī)制,另一個(gè)就是加鎖了。那么有沒(méi)有不加鎖的線程安全的單例實(shí)現(xiàn)嗎?有點(diǎn),那就是使用CAS。CAS是項(xiàng)樂(lè)觀鎖技術(shù),當(dāng)多個(gè)線程嘗試使用CAS同時(shí)更新同一個(gè)變量時(shí),只有其中一個(gè)線程能更新變量的值,而其它線程都失敗,失敗的線程并不會(huì)被掛起,而是被告知這次競(jìng)爭(zhēng)中失敗,并可以再次嘗試。代碼如下:

public class Singleton {private static final AtomicReference<Singleton> INSTANCE = new AtomicReference<Singleton>();private Singleton() {}public static Singleton getInstance() {for (;;) {Singleton singleton = INSTANCE.get();if (null != singleton) {return singleton;}singleton = new Singleton();if (INSTANCE.compareAndSet(null, singleton)) {return singleton;}}}}

用CAS的好處在于不需要使用傳統(tǒng)的鎖機(jī)制來(lái)保證線程安全,CAS是一種基于忙等待的算法,依賴(lài)底層硬件的實(shí)現(xiàn),相對(duì)于鎖它沒(méi)有線程切換和阻塞的額外消耗,可以支持較大的并行度。CAS的一個(gè)重要缺點(diǎn)在于如果忙等待一直執(zhí)行不成功(一直在死循環(huán)中),會(huì)對(duì)CPU造成較大的執(zhí)行開(kāi)銷(xiāo)。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶(hù)發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶(hù) 評(píng)論公約

    類(lèi)似文章 更多

    精品少妇人妻av一区二区蜜桃| 99久久免费看国产精品 | 国产超碰在线观看免费| 三级理论午夜福利在线看| 欧美不卡午夜中文字幕| 无套内射美女视频免费在线观看| 欧美一区二区三区喷汁尤物| 欧美日韩校园春色激情偷拍| 精品一区二区三区三级视频| 日本免费一本一二区三区| 久久热在线视频免费观看| 国产熟女一区二区精品视频| 丰满人妻一二区二区三区av| 亚洲中文字幕视频在线观看| 中文字幕亚洲在线一区| 欧美一区二区三区不卡高清视| 风韵人妻丰满熟妇老熟女av| 久久热这里只有精品视频| 亚洲黄香蕉视频免费看| 日本不卡片一区二区三区| 69久久精品亚洲一区二区| 国产无摭挡又爽又色又刺激| 男人大臿蕉香蕉大视频| 国产日韩欧美综合视频| 中国日韩一级黄色大片| 日韩精品免费一区三区| 东京不热免费观看日本| 91精品国自产拍老熟女露脸| 国产又黄又爽又粗视频在线| 91香蕉视频精品在线看| 九九热视频经典在线观看| 九九视频通过这里有精品| 美女被草的视频在线观看| 欧美日韩无卡一区二区| 男人大臿蕉香蕉大视频| 国产精品伦一区二区三区在线| 国产精品内射婷婷一级二级| 成人精品一级特黄大片| 欧美一区二区三区性视频 | 草草草草在线观看视频| 国产乱久久亚洲国产精品|