一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - Java設計模式之單例模式詳解

Java設計模式之單例模式詳解

2020-08-29 11:53byhieg Java教程

這篇文章主要為大家詳細介紹了Java設計模式之單例模式的相關資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下

單例模式是非常常見的設計模式,其含義也很簡單,一個類給外部提供一個唯一的實例。下文所有的代碼均在github

源碼整個項目不僅僅有設計模式,還有其他JavaSE知識點,歡迎Star,Fork

單例模式的UML圖

Java設計模式之單例模式詳解

單例模式的關鍵點

通過上面的UML圖,我們可以看出單例模式的特點如下:

1、構造器是私有的,不允許外部的類調用構造器
2、提供一個供外部訪問的方法,該方法返回單例類的實例

如何實現單例模式

上面已經給出了單例模式的關鍵點,我們的實現只需要滿足上面2點即可。但是正因為單例模式的實現方式比較寬松,所以不同的實現方式會有不同的問題。我們可以對單例模式的實現做一下分類,看一看有哪些不同的實現方式。

1根據單例對象的創建時機不同,可以分為餓漢模式和懶漢模式。餓漢是指在類加載的時候,就創建了對象。但是創建對象有時比較消耗資源,會造成類加載很慢,但是優點是獲取對象的速度很快,因為早已經創建好了嘛。懶漢就是相對餓漢而言,在需要返回單例對象的時候,在創建對象,類加載的時候,并不初始化,好處與缺點也不言而喻

2.根據是否實現線程安全,可以分為普通的懶漢模式這種線程不安全的寫法,和餓漢模式,雙重檢查鎖的懶漢模式,以及通過靜態內部類或者枚舉類等實現的線程安全的寫法。

一個線程不安全的單例模式

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class SimpleSingleton {
 
  private static SimpleSingleton simpleSingleton;
 
  private SimpleSingleton(){
 
  }
 
  public static SimpleSingleton getInstance(){
    if (simpleSingleton == null) {
      simpleSingleton = new SimpleSingleton();
    }
    return simpleSingleton;
  }
}

首先,我們可以看出這是一個懶漢模式的實現。因為只有在getInstance的時候,才會真正創建單例的對象。但是為什么他是線程不安全的呢,是因為可能會有2個線程同時進入if (simpleSingleton == null)的判斷,就是同時創建了simpleSingleton對象。

DCL懶漢模式

上面的方法可以看出是存在線程不安全的問題的,我們可以用同步關鍵字synchronized來實現線程安全。我們先逐步分析,先用synchronized來改寫上面的懶漢模式,代碼如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class DCLSingleton {
 
  private static DCLSingleton singleton;
  private DCLSingleton(){
  }
 
  public synchronized static DClSingleton getSingleton(){
    if (singleton == null) {
      singleton = new DCLSingleton();
    }
    return singleton;
  }
 
}

這樣,就有效的保證了不會有兩個線程同時執行該方法,但這個效率也太低了吧。因為在創建實例之后,每次得到實例對象,還是需要進行同步,synchronized的同步保證代價是比較大的,因此可以在此基礎上進行改造。在已經創建好之后,就不需要同步了,我們可以改成如下的形式:

?
1
2
3
4
5
6
7
8
9
10
public static DCLSingleton getSingleton(){
  if (singleton == null) {
    synchronized (DCLSingleton.class) {
      if (singleton == null) {
        singleton = new DCLSingleton();
      }
    }
  }
  return singleton;
}

其他代碼不變,只看這個方法。該方法的兩重if (singleton == null)可以有效地保證線程安全。比如,當兩個線程同時進入該方法的時候,第一個if,兩者都是進入,下面的代碼,但是碰到同步代碼塊,只能有一個先進入,進入的時候,繼續判斷,再次判斷為空,才會真正創建對象。如果不進行,第二個判斷,那些對于第一個進入的線程而言,確實創建了對象,但是第二個線程,他緊接著也會執行創建對象的操作,因為不知道第一個線程已經創建成功。因此,需要兩次判空。
但是真的就如此簡單的保證了線程安全嗎?我們仔細分析一下這個過程,singleton = new DCLSingleton();這個代碼實際上是3個操作。

1.給DCLSingleton實例分配內存
2.調用DCLSingleton()的構造函數,初始化成員字段
3.將singleton對象指向分配的內存空間。

在JDK1.5以前,上面的3個執行順序是不固定的,有可能是1-2-3,或者1-3-2。如果是1-3-2,則在第一個線程執行完第三步以后,第二個線程立即執行,但還沒有真正的進行初始化,所以就會使用的時候出錯。在JDK1.5以后,我們可以用volatile關鍵字來保證該1-2-3的順序執行。所以,除了getSingleton()方法要改成上面的樣子以外,還需要對private static DCLSingleton singleton; 改寫成private static volatile DCLSingleton singleton; 這樣,就真正保證了線程同步的懶漢寫法的單例模式。

餓漢寫法

餓漢寫法有很多變形,但無論是哪一種變形,都能保證線程安全,因為餓漢寫法是在類加載的時候,就完成了對象的初始化,類加載保證了他們天生是線程安全的。下面給出常見的2中餓漢寫法

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class HungrySingleton {
  private static final HungrySingleton singleton = new HungrySingleton();
 
  private HungrySingleton(){
 
  }
 
  public static HungrySingleton getSingleton(){
    return singleton;
  }
}
public class HungrySingleton {
  private static final HungrySingleton singleton = new HungrySingleton();
 
  private HungrySingleton(){
 
  }
 
//  public static HungrySingleton getSingleton(){
//    return singleton;
//  }
}

這兩種對初始化單例的對象上面,都是一致的, 通過final來保證對象的唯一。不同的是,調用單例對象的方式,第一種是通過getSingleton()第二種是通過類.類變量的形式。

靜態內部類實現單例模式

雙重檢查鎖(DCL)實現單例模式,雖然解決了線程不安全的問題,以及保證了資源的懶加載,在需要的時候,才會進行實例化的操作。但是在某些情況下(比如JDK低于1.5)會出現DCL失效,所以有一種很簡潔且依舊是懶加載的方法實現單例模式。寫法如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
public class StaticSingleton {
 
  private StaticSingleton(){
  }
  public static final StaticSingleton getInstance(){
    return Holder.singleton;
  }
 
  private static class Holder{
    private static final StaticSingleton singleton = new StaticSingleton();
  }
}

通過靜態內部類的形式,實現單例類的初始化,其特性同樣是通過ClassLoader來保證其單例對象的唯一,但是這是懶加載的,因為只有在Holder類被調用的時候,即getInstance調用的時候,才會加載Holder類從而實現創建對象。

枚舉類實現單例模式

直接看代碼:

?
1
2
3
4
5
6
public enum EnumSingleton {
  SINGLETON;
  public void doSometings(){
    
  }
}

使用的時候,直接通過EnumSingleton.SINGLETON.doSomethings()枚舉類天生特性是保證不會有兩個實例,并且只有在第一次訪問的時候才會被實例化,是懶加載的情況。

真的不會再次創建新的對象嗎?

在常規調用單例類的getInstance()方法的情況下,使用線程安全的寫法確實不會創建新的對象,但是Java提供了很多奇特的技巧和使用,下面這些使用會破壞掉常規的單例。

  • 反序列化
  • 反射
  • 克隆
  • 分布式環境下,多個類加載器

在除了枚舉實現單例模式的方法以外,其余所有方法碰到上述四種情況,都會重新創建對象。原因如下:

  • 反序列化會調用一個特殊的readResolve()方法來創建新的對象。我們可以重寫該方法,讓他返回原來的instance,而不是重新創建一個。
  • 反射會得到私有的構造函數,只能在構造函數中加一個判斷,如果對象不為null,則扔出一個運行時異常,如果不這樣,只有枚舉能解決,因為枚舉自帶的特性。
  • 克隆,因為直接拷貝的內存空間的內容,所以只有自己重寫單例類的clone方法,如果不這樣,也只有枚舉能解決,因為枚舉沒有克隆方法。
  • 多分布式環境,因為我們上述很多種單例的寫法,都是依賴于類加載器的特性,但是static的作用只負責到類加載器,所以當工程中存在多個類加載器的時候,就會創建多個實例,這種通常就需要第三方庫來解決。

什么時候用單例模式,用哪一種寫法的單例模式

單例模式有兩種比較適合的使用場景。
第一種是創建某個對象,需要的代價比較大,為了避免頻繁的創建和銷毀對象從而引起的對資源的浪費,會考慮使用單例模式。
第二種是這個對象必須只有一個,有多個會造成不可預估的錯誤,或者程序的混亂,比如只會有一個序號生成器,一個緩存等等。
針對使用的單例模式,如果需要理解的加載資源,就是用餓漢寫法,在Android應用中,很多對象需要在啟動的時候,立即就使用,比如啟動時,需要拉取相機配置的類管理縮略圖的cache類等等。如果不是立即需要,或者不是貫穿應用始終的,就不需要使用餓漢寫法,可以考慮懶漢寫法用(DCL或者靜態內部類實現)這兩種在一般情況下都不會出現問題。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 日本欧美不卡一区二区三区在线 | 男男同志videos | 高清国产激情视频在线观看 | 美女无遮挡 | 俄罗斯毛片免费大全 | 小女生RAPPER入口 | 亚洲图片一区二区三区 | 狠狠干在线观看 | 草莓视频网站18勿进 | yjzz视频 | 国产精品成人一区二区 | 1024国产看片在线观看 | 午夜国产精品视频在线 | 免费成人在线观看视频 | 久久国产乱子伦精品免费不卡 | 日本免费精品视频 | 国产全部理论片线观看 | 美女林柏欣21p人体之仓之梦 | 亚洲精品AV无码喷奶水糖心 | 四虎论坛| 色导行 | 亚洲欧美久久婷婷爱综合一区天堂 | 91麻豆精东果冻天美传媒老狼 | 国产乱码一卡二卡3卡四卡 国产乱插 | ady@ady9.映画网| 国产精品视频人人做人人爱 | www.青青操| a∨在线观看 | 国产午夜小视频 | 奇米影视7777 | 午夜福利电影网站鲁片大全 | 99国产情在线视频 | 欧美特欧美特级一片 | 久久99精国产一区二区三区四区 | 日本黄大片影院一区二区 | 精品综合久久久久久8888 | 波多野结衣教师未删减版 | 日本欧美不卡一区二区三区在线 | 69日本xxⅹxxxxx19 | 色婷婷久久综合中文久久一本` | 农村老妇1乱69系列小说 |