亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 開發 > Java > 正文

詳解Java實現單例的五種方式

2024-07-14 08:41:25
字體:
來源:轉載
供稿:網友

1. 什么是單例模式

單例模式指的是在應用整個生命周期內只能存在一個實例。單例模式是一種被廣泛使用的設計模式。他有很多好處,能夠避免實例對象的重復創建,減少創建實例的系統開銷,節省內存。

單例模式的要求有三點:

  1. 某個類只能有一個實例
  2. 它必須自行創建這個實例
  3. 他必須自行向整個系統提供整個實例

2. 單例模式和靜態類的區別

首先理解一下什么是靜態類,靜態類就是一個類里面都是靜態方法和靜態field,構造器被private修飾,因此不能被實例化。Math類就是一個靜態類。

知道了什么是靜態類后,來說一下他們兩者之間的區別:

1)首先單例模式會提供給你一個全局唯一的對象,靜態類只是提供給你很多靜態方法,這些方法不用創建對象,通過類就可以直接調用;

2)單例模式的靈活性更高,方法可以被override,因為靜態類都是靜態方法,所以不能被override;

3)如果是一個非常重的對象,單例模式可以懶加載,靜態類就無法做到;

那么時候時候應該用靜態類,什么時候應該用單例模式呢?首先如果你只是想使用一些工具方法,那么最好用靜態類,靜態類比單例類更快,因為靜態的綁定是在編譯期進行的。如果你要維護狀態信息,或者訪問資源時,應該選用單例模式。還可以這樣說,當你需要面向對象的能力時(比如繼承、多態)時,選用單例類,當你僅僅是提供一些方法時選用靜態類。

3.如何實現單例模式

1. 餓漢模式

所謂餓漢模式就是立即加載,一般情況下再調用getInstancef方法之前就已經產生了實例,也就是在類加載的時候已經產生了。這種模式的缺點很明顯,就是占用資源,當單例類很大的時候,其實我們是想使用的時候再產生實例。因此這種方式適合占用資源少,在初始化的時候就會被用到的類。

class SingletonHungary {  private static SingletonHungary singletonHungary = new SingletonHungary();  //將構造器設置為private禁止通過new進行實例化  private SingletonHungary() {  }  public static SingletonHungary getInstance() {    return singletonHungary;  }}

2. 懶漢模式

懶漢模式就是延遲加載,也叫懶加載。在程序需要用到的時候再創建實例,這樣保證了內存不會被浪費。針對懶漢模式,這里給出了5種實現方式,有些實現方式是線程不安全的,也就是說在多線程并發的環境下可能出現資源同步問題。

首先第一種方式,在單線程下沒問題,在多線程下就出現問題了。

// 單例模式的懶漢實現1--線程不安全class SingletonLazy1 {  private static SingletonLazy1 singletonLazy;  private SingletonLazy1() {  }  public static SingletonLazy1 getInstance() {    if (null == singletonLazy) {      try {        // 模擬在創建對象之前做一些準備工作        Thread.sleep(1000);      } catch (InterruptedException e) {        e.printStackTrace();      }      singletonLazy = new SingletonLazy1();    }    return singletonLazy;  }}

我們模擬10個異步線程測試一下:

public class SingletonLazyTest {  public static void main(String[] args) {    Thread2[] ThreadArr = new Thread2[10];    for (int i = 0; i < ThreadArr.length; i++) {      ThreadArr[i] = new Thread2();      ThreadArr[i].start();    }  }}// 測試線程class Thread2 extends Thread {  @Override  public void run() {    System.out.println(SingletonLazy1.getInstance().hashCode());  }}

運行結果:

124191239
124191239
872096466
1603289047
1698032342
1913667618
371739364
124191239
1723650563
367137303

可以看到他們的hashCode不都是一樣的,說明在多線程環境下,產生了多個對象,不符合單例模式的要求。

那么如何使線程安全呢?第二種方法,我們使用synchronized關鍵字對getInstance方法進行同步。

// 單例模式的懶漢實現2--線程安全// 通過設置同步方法,效率太低,整個方法被加鎖class SingletonLazy2 {  private static SingletonLazy2 singletonLazy;  private SingletonLazy2() {  }  public static synchronized SingletonLazy2 getInstance() {    try {      if (null == singletonLazy) {        // 模擬在創建對象之前做一些準備工作        Thread.sleep(1000);        singletonLazy = new SingletonLazy2();      }    } catch (InterruptedException e) {      e.printStackTrace();    }    return singletonLazy;  }}

使用上面的測試類,測試結果:

1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989

可以看到,這種方式達到了線程安全。但是缺點就是效率太低,是同步運行的,下個線程想要取得對象,就必須要等上一個線程釋放,才可以繼續執行。

那我們可以不對方法加鎖,而是將里面的代碼加鎖,也可以實現線程安全。但這種方式和同步方法一樣,也是同步運行的,效率也很低。

// 單例模式的懶漢實現3--線程安全// 通過設置同步代碼塊,效率也太低,整個代碼塊被加鎖class SingletonLazy3 {  private static SingletonLazy3 singletonLazy;  private SingletonLazy3() {  }  public static SingletonLazy3 getInstance() {    try {      synchronized (SingletonLazy3.class) {        if (null == singletonLazy) {          // 模擬在創建對象之前做一些準備工作          Thread.sleep(1000);          singletonLazy = new SingletonLazy3();        }      }    } catch (InterruptedException e) {      // TODO: handle exception    }    return singletonLazy;  }}

我們來繼續優化代碼,我們只給創建對象的代碼進行加鎖,但是這樣能保證線程安全么?

// 單例模式的懶漢實現4--線程不安全// 通過設置同步代碼塊,只同步創建實例的代碼// 但是還是有線程安全問題class SingletonLazy4 {  private static SingletonLazy4 singletonLazy;  private SingletonLazy4() {  }  public static SingletonLazy4 getInstance() {    try {      if (null == singletonLazy) {    //代碼1        // 模擬在創建對象之前做一些準備工作        Thread.sleep(1000);        synchronized (SingletonLazy4.class) {          singletonLazy = new SingletonLazy4(); //代碼2        }      }    } catch (InterruptedException e) {      // TODO: handle exception    }    return singletonLazy;  }}

我們來看一下運行結果:

1210004989
1425839054
1723650563
389001266
1356914048
389001266
1560241484
278778395
124191239
367137303

從結果看來,這種方式不能保證線程安全,為什么呢?我們假設有兩個線程A和B同時走到了‘代碼1',因為此時對象還是空的,所以都能進到方法里面,線程A首先搶到鎖,創建了對象。釋放鎖后線程B拿到了鎖也會走到‘代碼2',也創建了一個對象,因此多線程環境下就不能保證單例了。

讓我們來繼續優化一下,既然上述方式存在問題,那我們在同步代碼塊里面再一次做一下null判斷不就行了,這種方式就是我們的DCL雙重檢查鎖機制。

//單例模式的懶漢實現5--線程安全//通過設置同步代碼塊,使用DCL雙檢查鎖機制//使用雙檢查鎖機制成功的解決了單例模式的懶漢實現的線程不安全問題和效率問題//DCL 也是大多數多線程結合單例模式使用的解決方案//第一個if判斷的作用:是為了提高程序的 效率,當SingletonLazy5對象被創建以后,再獲取SingletonLazy5對象時就不用去驗證同步代碼塊的鎖及后面的代碼,直接返回SingletonLazy5對象//第二個if判斷的作用:是為了解決多線程下的安全性問題,也就是保證對象的唯一。class SingletonLazy5 {  private static volatile SingletonLazy5 singletonLazy;  private SingletonLazy5() {  }  public static SingletonLazy5 getInstance() {    try {      if (null == singletonLazy) {        // 模擬在創建對象之前做一些準備工作        Thread.sleep(1000);        synchronized (SingletonLazy5.class) {          if(null == singletonLazy) {            singletonLazy = new SingletonLazy5();          }        }      }    } catch (InterruptedException e) {    }    return singletonLazy;  }}

運行結果:

124191239
124191239
124191239
124191239
124191239
124191239
124191239
124191239
124191239
124191239

我們可以看到DCL雙重檢查鎖機制很好的解決了懶加載單例模式的效率問題和線程安全問題。這也是我們最常用到的方式。

volatile關鍵字

這里注意到在定義singletonLazy的時候用到了volatile關鍵字,這是為了防止指令重排序的,為什么要這么做呢,我們來看一個場景:

代碼走到了 singletonLazy = new SingletonLazy5();看起來是一句話,但這并不是一個原子操作(要么全部執行完,要么全部不執行,不能執行一半),這句話被編譯成8條匯編指令,大致做了3件事情:

1.給SingletonLazy5的實例分配內存。

2.初始化SingletonLazy5的構造器

3.將singletonLazy對象指向分配的內存空間(注意到這步instance就非null了)。

由于Java編譯器允許處理器亂序執行(out-of-order),以及JDK1.5之前JMM(Java Memory Medel)中Cache、寄存器到主內存回寫順序的規定,上面的第二點和第三點的順序是無法保證的,也就是說,執行順序可能是1-2-3也可能是1-3-2,如果是后者,并且在3執行完畢、2未執行之前,被切換到線程二上,這時候singletonLazy因為已經在線程一內執行過了第三點,singletonLazy已經是非空了,所以線程二直接拿走singletonLazy,然后使用,然后順理成章地報錯,而且這種難以跟蹤難以重現的錯誤估計調試上一星期都未必能找得出來。

DCL的寫法來實現單例是很多技術書、教科書(包括基于JDK1.4以前版本的書籍)上推薦的寫法,實際上是不完全正確的。的確在一些語言(譬如C語言)上DCL是可行的,取決于是否能保證2、3步的順序。在JDK1.5之后,官方已經注意到這種問題,因此調整了JMM、具體化了volatile關鍵字,因此如果JDK是1.5或之后的版本,只需要將singletonLazy的定義加上volatile關鍵字,就可以保證每次都去singletonLazy都從主內存讀取,并且可以禁止重排序,就可以使用DCL的寫法來完成單例模式。當然volatile或多或少也會影響到性能,最重要的是我們還要考慮JDK1.42以及之前的版本,所以單例模式寫法的改進還在繼續。

3. 靜態內部類

基于上面的考慮,我們可以使用靜態內部類實現單例模式,代碼如下:

//使用靜態內部類實現單例模式--線程安全class SingletonStaticInner {  private SingletonStaticInner() {  }  private static class SingletonInner {    private static SingletonStaticInner singletonStaticInner = new SingletonStaticInner();  }  public static SingletonStaticInner getInstance() {    try {      Thread.sleep(1000);    } catch (InterruptedException e) {      // TODO Auto-generated catch block      e.printStackTrace();    }    return SingletonInner.singletonStaticInner;  }}

可以看到使用這種方式我們沒有顯式的進行任何同步操作,那他是如何保證線程安全呢?和餓漢模式一樣,是靠JVM保證類的靜態成員只能被加載一次的特點,這樣就從JVM層面保證了只會有一個實例對象。那么問題來了,這種方式和餓漢模式又有什么區別呢?不也是立即加載么?實則不然,加載一個類時,其內部類不會同時被加載。一個類被加載,當且僅當其某個靜態成員(靜態域、構造器、靜態方法等)被調用時發生。

可以說這種方式是實現單例模式的最優解。

4. 靜態代碼塊

這里提供了靜態代碼塊實現單例模式。這種方式和第一種類似,也是一種餓漢模式。

//使用靜態代碼塊實現單例模式class SingletonStaticBlock {  private static SingletonStaticBlock singletonStaticBlock;  static {    singletonStaticBlock = new SingletonStaticBlock();  }  public static SingletonStaticBlock getInstance() {    return singletonStaticBlock;  }}

5. 序列化與反序列化

LZ為什么要提序列化和反序列化呢?因為單例模式雖然能保證線程安全,但在序列化和反序列化的情況下會出現生成多個對象的情況。運行下面的測試類,

public class SingletonStaticInnerSerializeTest {  public static void main(String[] args) {    try {      SingletonStaticInnerSerialize serialize = SingletonStaticInnerSerialize.getInstance();      System.out.println(serialize.hashCode());      //序列化      FileOutputStream fo = new FileOutputStream("tem");      ObjectOutputStream oo = new ObjectOutputStream(fo);      oo.writeObject(serialize);      oo.close();      fo.close();      //反序列化      FileInputStream fi = new FileInputStream("tem");      ObjectInputStream oi = new ObjectInputStream(fi);      SingletonStaticInnerSerialize serialize2 = (SingletonStaticInnerSerialize) oi.readObject();      oi.close();      fi.close();      System.out.println(serialize2.hashCode());    } catch (Exception e) {      e.printStackTrace();    }  }}//使用匿名內部類實現單例模式,在遇見序列化和反序列化的場景,得到的不是同一個實例//解決這個問題是在序列化的時候使用readResolve方法,即去掉注釋的部分class SingletonStaticInnerSerialize implements Serializable {  /**   * 2018年03月28日   */  private static final long serialVersionUID = 1L;  private static class InnerClass {    private static SingletonStaticInnerSerialize singletonStaticInnerSerialize = new SingletonStaticInnerSerialize();  }  public static SingletonStaticInnerSerialize getInstance() {    return InnerClass.singletonStaticInnerSerialize;  }//  protected Object readResolve() {//    System.out.println("調用了readResolve方法");//    return InnerClass.singletonStaticInnerSerialize;//  }}

可以看到:

865113938
1078694789

結果表明的確是兩個不同的對象實例,違背了單例模式,那么如何解決這個問題呢?解決辦法就是在反序列化中使用readResolve()方法,將上面的注釋代碼去掉,再次運行:

865113938
調用了readResolve方法
865113938

問題來了,readResolve()方法到底是何方神圣,其實當JVM從內存中反序列化地"組裝"一個新對象時,就會自動調用這個 readResolve方法來返回我們指定好的對象了, 單例規則也就得到了保證。readResolve()的出現允許程序員自行控制通過反序列化得到的對象。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VeVb武林網。


注:相關教程知識閱讀請移步到JAVA教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
欧美精品电影免费在线观看| 欧美亚洲在线观看| 久久精品这里热有精品| 日韩极品精品视频免费观看| 国产69精品久久久久9999| 国产精品久久久久久久久久新婚| 国产一区二区三区久久精品| 欧美插天视频在线播放| 国内成人精品一区| 久久综合久久美利坚合众国| 亚洲国产成人精品久久| 精品亚洲精品福利线在观看| 精品少妇v888av| 黄色精品在线看| 宅男66日本亚洲欧美视频| 色哟哟网站入口亚洲精品| 日韩欧美在线中文字幕| 国产精品户外野外| 777精品视频| 国产ts人妖一区二区三区| 国产99视频在线观看| 欧美一级视频在线观看| 久久精品中文字幕电影| 亚洲人成毛片在线播放| 国产精品一区二区三区成人| 91精品国产自产在线老师啪| 久久99视频免费| 欧美午夜www高清视频| 社区色欧美激情 | 欧美日韩国产精品一区二区三区四区| 一本一道久久a久久精品逆3p| 国产99久久精品一区二区永久免费| 国产精品青青在线观看爽香蕉| 91超碰caoporn97人人| 欧美国产一区二区三区| 日韩av中文字幕在线免费观看| 国产精品 欧美在线| 久久久久久久久久久91| 久久精品青青大伊人av| 亚洲欧美日韩网| 日韩欧美在线观看视频| 欧美成aaa人片在线观看蜜臀| 一区二区三区精品99久久| 亚洲人成网站999久久久综合| 亚洲国产精品va在线看黑人| 亚洲国产一区二区三区在线观看| 国产精品久久久久久一区二区| 国产精品自拍视频| 97视频在线观看视频免费视频| 九九热精品在线| 亚洲欧美激情一区| 亚洲自拍偷拍第一页| 日韩视频永久免费观看| 亚洲丝袜av一区| 欧美日韩亚洲精品一区二区三区| 中文字幕亚洲专区| 欧美xxxx18国产| 一本色道久久综合亚洲精品小说| 欧美日韩成人黄色| 欧美一级高清免费| 成人免费观看49www在线观看| 国产成人aa精品一区在线播放| 亚州精品天堂中文字幕| 国产一区二区三区毛片| 久久久久久欧美| 欧美日韩国产成人在线| 欧美性黄网官网| 久久精品99久久香蕉国产色戒| 色综合久久中文字幕综合网小说| 久久影视免费观看| 亚洲va欧美va在线观看| 日韩电影中文字幕在线| 亚洲a在线观看| 精品久久久久国产| 精品亚洲国产成av人片传媒| 尤物九九久久国产精品的分类| 高清亚洲成在人网站天堂| 亚洲一区第一页| 91欧美日韩一区| 日韩美女免费视频| 国产午夜精品久久久| 精品视频www| 亚洲欧美成人在线| 精品久久久久久国产91| 亚洲精品小视频在线观看| 国产精品视频区| 亚洲国产免费av| 日韩美女主播视频| 欧美精品在线免费播放| 欧美华人在线视频| 一区二区欧美亚洲| 在线视频日韩精品| 九九精品视频在线观看| 91欧美视频网站| 一区二区三区视频免费在线观看| 国内外成人免费激情在线视频网站| 欧美成人h版在线观看| 久久夜色精品亚洲噜噜国产mv| 久操成人在线视频| 精品成人乱色一区二区| 久久久久久国产精品美女| 国产精品高清网站| 色婷婷亚洲mv天堂mv在影片| 日韩中文字幕在线视频| 欧美诱惑福利视频| 亚洲高清av在线| 欧美性猛交xxxx乱大交极品| 亚洲国产精品嫩草影院久久| 色婷婷综合成人av| 国内精品久久久久久| 久久99热精品| 2018国产精品视频| 国产精品日韩欧美| 国产欧美日韩中文字幕在线| 精品国产乱码久久久久酒店| 成人午夜一级二级三级| 91精品国产高清自在线看超| 在线电影中文日韩| 国内免费久久久久久久久久久| 国产精品444| 97人人爽人人喊人人模波多| 黑人巨大精品欧美一区二区一视频| 欧美最猛性xxxx| 欧美日韩在线观看视频| 欧美亚洲成人xxx| 国产精品美乳一区二区免费| 亚洲欧美日韩中文在线| 国产91精品在线播放| 国产欧美va欧美va香蕉在线| 亚洲精品wwww| 插插插亚洲综合网| 久久精品免费播放| 亚洲综合在线小说| 亚洲一区国产精品| 成人欧美一区二区三区黑人| 亚洲第一视频在线观看| 亚洲免费伊人电影在线观看av| 亚洲视频一区二区三区| 欧美亚洲另类激情另类| 狠狠色狠狠色综合日日五| 久久精品美女视频网站| 国产精品色午夜在线观看| 福利视频一区二区| 国语对白做受69| 中文字幕在线看视频国产欧美| 亚洲一区二区福利| 欧美日韩国产一中文字不卡| 伊人久久综合97精品| 欧美激情亚洲激情| 高清亚洲成在人网站天堂| 日本久久久久久久久| 国内精品久久久久久久久| 一区二区三区高清国产| 精品久久久久久亚洲国产300| 日韩精品视频在线观看免费| 福利视频一区二区| 成人疯狂猛交xxx| 欧美一区二区三区免费观看| 欧美亚洲激情视频| 91av国产在线| 成人免费网视频| 久久精品视频在线播放| 国产精品高潮呻吟视频| 亚洲国内精品视频|