日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

单例模式(Singleton-Pattern)百媚生

發(fā)布時間:2025/6/17 编程问答 46 豆豆
生活随笔 收集整理的這篇文章主要介紹了 单例模式(Singleton-Pattern)百媚生 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

  1 動機

  對于系統(tǒng)中的某些類來說,只有一個實例很重要,例如,一個系統(tǒng)中可以存在多個打印任務,但是只能有一個正在工作的任務;一個系統(tǒng)只能有一個窗口管理器或文件系統(tǒng);一個系統(tǒng)只能有一個計時工具或ID(序號)生成器。

  如何保證一個類只有一個實例并且這個實例易于被訪問呢?定義一個全局變量可以確保對象隨時都可以被訪問,但不能防止我們實例化多個對象。

  一個更好的解決辦法是讓類自身負責保存它的唯一實例。這個類可以保證沒有其他實例被創(chuàng)建,并且它可以提供一個訪問該實例的方法。這就是單例模式的模式動機。

  #2 定義

  單例模式確保某一個類只有一個實例,而且自行實例化并向整個系統(tǒng)提供這個實例,這個類稱為單例類,它提供全局訪問的方法。

  單例模式的要點有三個

  某個類只能有一個實例

  它必須自行創(chuàng)建這個實例

  它必須自行向整個系統(tǒng)提供這個實例。單例模式是一種對象創(chuàng)建型模式。單例模式又名單件模式或單態(tài)模式。

  優(yōu)點: 1、在內存里只有一個實例,減少了內存的開銷,尤其是頻繁的創(chuàng)建和銷毀實例(比如管理學院首頁頁面緩存)。 2、避免對資源的多重占用(比如寫文件操作)。

  **缺點:**沒有接口,不能繼承,與單一職責原則沖突,一個類應該只關心內部邏輯,而不關心外面怎么樣來實例化

  使用場景:

  1、要求生產唯一序列號。

  2、WEB 中的計數器,不用每次刷新都在數據庫里加一次,用單例先緩存起來。

  3、創(chuàng)建的一個對象需要消耗的資源過多,比如 I/O 與數據庫的連接等。

  注意事項: getInstance() 方法中需要使用同步鎖 synchronized (Singleton.class) 防止多線程同時進入造成 instance 被多次實例化。

  結構

  


  


  


  分析

  單例模式的目的是保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。單例模式包含的角色只有一個,就是單例類——Singleton。單例類擁有一個私有構造函數,確保用戶無法通過new關鍵字直接實例化它。除此之外,該模式中包含一個靜態(tài)私有成員變量與靜態(tài)公有的工廠方法,該工廠方法負責檢驗實例的存在性并實例化自己,然后存儲在靜態(tài)成員變量中,以確保只有一個實例被創(chuàng)建。

  在單例模式的實現過程中,需要注意如下三點:

  單例類的構造函數為私有;

  提供一個自身的靜態(tài)私有成員變量;

  提供一個公有的靜態(tài)工廠方法。

  優(yōu)點

  提供了對唯一實例的受控訪問。因為單例類封裝了它的唯一實例,所以它可以嚴格控制客戶怎樣以及何時訪問它,并為設計及開發(fā)團隊提供了共享的概念

  由于在系統(tǒng)內存中只存在一個對象,因此可以節(jié)約系統(tǒng)資源,對于一些需要頻繁創(chuàng)建和銷毀的對象,單例模式無疑可以提高系統(tǒng)的性能。

  允許可變數目的實例。我們可以基于單例模式進行擴展,使用與單例控制相似的方法來獲得指定個數的對象實例。

  缺點

  由于單例模式中沒有抽象層,因此單例類的擴展困難

  單例類的職責過重,在一定程度上違背了“單一職責原則”。

  因為單例類既充當了工廠角色,提供了工廠方法,同時又充當了產品角色,包含一些業(yè)務方法,將產品的創(chuàng)建和產品的本身的功能融合到一起。

  濫用單例將帶來一些負面問題,如

  為了節(jié)省資源將數據庫連接池對象設計為單例類,可能會導致共享連接池對象的程序過多而出現連接池溢出

  現在很多面向對象語言的運行環(huán)境都提供了自動垃圾回收的技術,因此,如果實例化的對象長時間不被利用,系統(tǒng)會認為它是垃圾,會自動銷毀并回收資源,下次利用時又將重新實例化,這將導致對象狀態(tài)的丟失。

  #適用場景

  系統(tǒng)只需要一個實例對象,如

  系統(tǒng)要求提供一個唯一的序列號生成器

  需要考慮資源消耗太大而只允許創(chuàng)建一個對象

  客戶調用類的單個實例只允許使用一個公共訪問點,除了該公共訪問點,不能通過其他途徑訪問該實例。

  在一個系統(tǒng)中要求一個類只有一個實例時才應當使用單例模式。反過來,如果一個類可以有幾個實例共存,就需要對單例模式進行改進,使之成為多例模式

  應用

  一個具有自動編號主鍵的表可以有多個用戶同時使用,但數據庫中只能有一個地方分配下一個主鍵編號,否則會出現主鍵重復,因此該主鍵編號生成器必須具備唯一性,可以通過單例模式來實現。

  總結

  單例模式確保某一個類只有一個實例,而且自行實例化并向整個系統(tǒng)提供這個實例,這個類稱為單例類,它提供全局訪問的方法。單例模式的要點有三個:一是某個類只能有一個實例;二是它必須自行創(chuàng)建這個實例;三是它必須自行向整個系統(tǒng)提供這個實例。單例模式是一種對象創(chuàng)建型模式。

  單例模式只包含一個單例角色:在單例類的內部實現只生成一個實例,同時它提供一個靜態(tài)的工廠方法,讓客戶可以使用它的唯一實例;為了防止在外部對其實例化,將其構造函數設計為私有。

  單例模式的目的是保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。單例類擁有一個私有構造函數,確保用戶無法通過new關鍵字直接實例化它。除此之外,該模式中包含一個靜態(tài)私有成員變量與靜態(tài)公有的工廠方法。該工廠方法負責檢驗實例的存在性并實例化自己,然后存儲在靜態(tài)成員變量中,以確保只有一個實例被創(chuàng)建。

  單例模式的主要優(yōu)點在于提供了對唯一實例的受控訪問并可以節(jié)約系統(tǒng)資源;其主要缺點在于因為缺少抽象層而難以擴展,且單例類職責過重。

  單例模式適用情況包括:系統(tǒng)只需要一個實例對象;客戶調用類的單個實例只允許使用一個公共訪問點。

  實現方式

  1、懶漢式(非線程安全)

  最基本的實現方式

  不支持多線程。因為沒有加鎖 synchronized,所以嚴格意義上并不算單例模式

  這種方式 lazy loading 很明顯,不要求線程安全,當有多個線程并行調用 getInstance() 的時候,就會創(chuàng)建多個實例。也就是說在多線程下不能正常工作。

  public class Singleton {

  private static Singleton instance;

  private Singleton (){}

  public static Singleton getInstance() {

  if (instance == null) {

  instance = new Singleton();

  }

  return instance;

  }

  }

  2、懶漢式(線程安全)

  為了解決上面的問題,最簡單的方法是將整個 getInstance() 方法設為同步(synchronized)

  優(yōu)點:第一次調用才初始化,避免內存浪費。

  缺點:必須加鎖 synchronized 才能保證單例,但加鎖會影響效率。

  雖然做到了線程安全,并解決了多實例的問題,但并不高效。

  因為在任何時候只能有一個線程調用 getInstance()

  但是同步操作只需要在第一次調用時才被需要,即第一次創(chuàng)建單例實例對象時。

  這就引出了雙重檢驗鎖。

  public class Singleton {

  private static volatile Singleton INSTANCE = null;

  // Private constructor suppresses

  // default public constructor

  private Singleton() {}

  //thread safe and performance promote

  public static Singleton getInstance() {

  if(INSTANCE == null){

  synchronized(Singleton.class){

  //when more than two threads run into the first null check same time, to avoid instanced more than one time, it needs to be checked again.

  if(INSTANCE == null){

  INSTANCE = new Singleton();

  }

  }

  }

  return INSTANCE;

  }

  }

  ##3 餓漢式

  較常用,但易產生垃圾對象

  優(yōu)點:無鎖,執(zhí)行效率提高

  缺點:類加載時就初始化,浪費內存

  非常簡單,實例被聲明成 static 和 final變量了,在第一次加載類到內存中時就會初始化,所以創(chuàng)建實例本身是線程安全的。

  它基于類加載機制避免了多線程的同步問題

  不過,instance在類裝載時就實例化,雖然導致類裝載的原因有很多種,在單例模式中大多數都是調用 getInstance, 但也不能確定有其他的方式(或者其他的靜態(tài)方法)導致類裝載,這時候初始化instance 顯然沒有達到lazy loading

  public class Singleton {

  private final static Singleton INSTANCE = new Singleton();

  // Private constructor suppresses

  private Singleton() {}

  // default public constructor

  public static Singleton getInstance() {

  return INSTANCE;

  }

  }

  這種寫法如果完美的話,就沒必要在啰嗦那么多雙檢鎖的問題了。

  缺點是它不是一種懶加載模式(lazy initialization),單例會在加載類后一開始就被初始化,即使客戶端沒有調用 getInstance()方法。

  餓漢式的創(chuàng)建方式在一些場景中將無法使用:譬如 Singleton 實例的創(chuàng)建是依賴參數或者配置文件的,在 getInstance() 之前必須調用某個方法設置參數給它,那樣這種單例寫法就無法使用了。

  4、雙重檢驗鎖模式(double checked locking pattern)

  一種使用同步塊加鎖的方法。程序員稱其為雙重檢查鎖,因為會有兩次檢查instance == null

  一次是在同步塊外

  一次是在同步塊內。為什么在同步塊內還要再檢驗一次?因為可能會有多個線程一起進入同步塊外的 if,如果在同步塊內不進行二次檢驗的話就會生成多個實例了。

  public class Singleton {

  private volatile static Singleton singleton;

  private Singleton (){}

  public static Singleton getSingleton() {

  if (singleton == null) { //Single Checked

  synchronized (Singleton.class) {

  if (singleton == null) { //Double Checked

  singleton = new Singleton();

  }

  }

  }

  return singleton;

  }

  }

  看起來很完美,很可惜哦,它還是有問題。

  主要在于

  instance = new Singleton()

  并非一個原子操作,事實上在 JVM 中這句話大概做了下面 3 件事情

  1、memory = allocate() 分配對象的內存空間

  2、ctorInstance() 調用 Singleton 的構造函數來初始化成員變量

  3、instance = memory 設置instance指向剛分配的內存執(zhí)行完這步 instance 就為非 null 了)

  JVM和CPU優(yōu)化,發(fā)生了指令重排

  但是在 JVM 的JIT 中存在指令重排序的優(yōu)化。

  也就是說上面的第2步和第3步的順序是不能保證的,最終的執(zhí)行順序可能是 1-2-3 也可能是 1-3-2。如果是后者

  1、memory = allocate() 分配對象的內存空間

  3、instance = memory 設置instance指向剛分配的內存

  2、ctorInstance() 初始化對象

  則在 3 執(zhí)行完畢、2 未執(zhí)行之前,被線程二搶占了,這時 instance 已經是非 null 了(但卻沒有初始化),所以線程二會直接返回 instance,然后使用,然后順理成章地報錯。

  只需要將 instance 變量聲明成volatile

  public class Singleton {

  private volatile static Singleton instance; //聲明成 volatile

  private Singleton (){}

  public static Singleton getSingleton() {

  if (instance == null) {

  synchronized (Singleton.class) {

  if (instance == null) {

  instance = new Singleton();

  }

  }

  }

  return instance;

  }

  }

  有些人認為使用 volatile 的原因是可見性,也就是可以保證線程在本地不會存有 instance 的副本,每次都是去主內存中讀取。

  但其實是不對的。使用 volatile 的主要原因是其另一個特性:禁止指令重排序優(yōu)化。

  在 volatile 變量的賦值操作后面會有一個內存屏障(生成的匯編代碼上),讀操作不會被重排序到內存屏障之前。

  比如上面的例子,取操作必須在執(zhí)行完 1-2-3 之后或者 1-3-2 之后,不存在執(zhí)行到 1-3 然后取到值的情況。從「先行發(fā)生原則」的角度理解的話,就是對于一個 volatile 變量的寫操作都先行發(fā)生于后面對這個變量的讀操作(這里的“后面”是時間上的先后順序)。

  但是特別注意在 Java 5 以前的版本使用了 volatile 的雙檢鎖還是有問題的。其原因是 Java 5 以前的 JMM (Java 內存模型)是存在缺陷的,即時將變量聲明成 volatile 也不能完全避免重排序,主要是 volatile 變量前后的代碼仍然存在重排序問題。這個 volatile 屏蔽重排序的問題在 Java 5 中才得以修復,所以在這之后才可以放心使用 volatile。

  相信你不會喜歡這種復雜又隱含問題的方式,當然我們有更好的實現線程安全的單例模式的辦法。

  #5、靜態(tài)內部類

  線程安全

  實現難度: 一般

  描述: 這種方式能達到雙檢鎖方式一樣的功效,但實現更簡單

  對靜態(tài)域使用延遲初始化,應使用這種方式而不是雙檢鎖方式

  這種方式只適用于靜態(tài)域的情況,雙檢鎖方式可在實例域需要延遲初始化時使用。

  這種方式同樣利用了 classloder 機制來保證初始化 instance 時只有一個線程,它跟第 3 種方式不同的是:第 3 種方式只要 Singleton 類被裝載了,那么 instance 就會被實例化(沒有達到 lazy loading 效果),而這種方式是 Singleton 類被裝載了,instance 不一定被初始化。因為 SingletonHolder 類沒有被主動使用,只有通過顯式調用 getInstance 方法時,才會顯式裝載 SingletonHolder 類,從而實例化 instance。想象一下,如果實例化 instance 很消耗資源,所以想讓它延遲加載,另外一方面,又不希望在 Singleton 類加載時就實例化,因為不能確保 Singleton 類還可能在其他的地方被主動使用從而被加載,那么這個時候實例化 instance 顯然是不合適的。這個時候,這種方式相比第 3 種方式就顯得很合理

  public class Singleton {

  private static class SingletonHolder {

  private static final Singleton INSTANCE = new Singleton();

  }

  private Singleton (){}

  public static final Singleton getInstance() {

  return SingletonHolder.INSTANCE;

  }

  }

  6 枚舉

  JDK5 起

  線程安全

  實現單例模式的最佳方法。

  它更簡潔,自動支持序列化機制,絕對防止多次實例化。

  Effective Java 作者 Josh Bloch 提倡的方式,它不僅能

  避免多線程同步問題

  自動支持序列化機制

  防止反序列化重新創(chuàng)建新的對象

  絕對防止多次實例化

  不能通過 reflection attack 來調用私有構造方法。

  public enum Singleton {

  INSTANCE;

  public void whateverMethod() {

  }

  }

  經驗

  一般情況下,不建議使用第 1 種和第 2 種懶漢方式,建議使用第 3 種餓漢方式。

  只有在要明確實現 lazy loading 效果時,才會使用第 5 種登記方式。

  如果涉及到反序列化創(chuàng)建對象時,可以嘗試使用第 6 種枚舉方式。

  如果有其他特殊的需求,可以考慮使用第 4 種雙檢鎖方式。

?


轉載于:https://juejin.im/post/5c3465a36fb9a049a81f7fac

總結

以上是生活随笔為你收集整理的单例模式(Singleton-Pattern)百媚生的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。