現在的位置: 首頁 > 架構設計 > 正文

什麼是單例模式?單例模式的八種姿態寫法

2020年02月25日 架構設計 ⁄ 共 7447字 ⁄ 字型大小 評論關閉

  何謂單例模式?

  單例模式是一種常用的軟體設計模式,其定義是單例對象的類只能允許一個實例存在。許多時候整個系統只需要擁有一個的全局對象,這樣有利於我們協調系統整體的行為。比如在某個伺服器程序中,該伺服器的配置信息存放在一個文件中,這些配置數據由一個單例對象統一讀取,然後服務進程中的其他對象再通過這個單例對象獲取這些配置信息。這種方式簡化了在複雜環境下的配置管理。

  單例模式,簡單的說就是 一個類只能有一個實例,並且在整個項目中都能訪問到這個實例。

  單例模式的優點:

  1、在內存中只有一個對象,節省內存空間。

  2、避免頻繁的創建銷毀對象,可以提高性能。

  3、避免對共享資源的多重佔用。

  4、可以全局訪問。

  單例模式實現整體思路流程

  首先我們要清楚單例模式要求類能夠有返回對象一個引用(永遠是同一個)和一個獲得該實例的方法(必須是靜態方法,通常使用getInstance這個名稱)。

  單例模式的常規實現思路大致相同為以下三個步驟:

  1、私有構造方法;

  2、指向自己實例的私有靜態引用;

  3、以自己實例為返回值的靜態的公有的方法。

  當然也可以理解為:

  1、私有化構造方法,讓外部不能new。

  2、本類內部創建對象實例【靜態變數目的是為了類載入的時候創建實例】。

  3、提供一個公有的static靜態方法(一般該方法使用getInstance這個名稱),返回實例對象。

  將該類的構造方法定義為私有方法,這樣其他處的代碼就無法通過調用該類的構造方法來實例化該類的對象,只有通過該類提供的靜態方法來得到該類的唯一實例;

  在該類內提供一個靜態方法,當我們調用這個方法時,如果類持有的引用不為空就返回這個引用,如果類保持的引用為空就創建該類的實例並將實例的引用賦予該類保持的引用。

  單例模式的適用場景

  由於單例模式有很多獨特的優點,所以是編程中用的比較多的一種設計模式。我總結了一下我所知道的適合使用單例模式的場景:

  1、需要頻繁實例化然後銷毀的對象。

  2、創建對象時耗時過多或者耗資源過多,但又經常用到的對象。

  3、有狀態的工具類對象。

  4、頻繁訪問資料庫或文件的對象。

  在Spring MVC框架中的controller 默認是單例模式的!

  單例模式的八種姿態寫法

  強烈建議:如果是沒有接觸單例模式的讀者朋友強烈建議你們動手敲一遍,不要複製,不然沒效果!

  還有一點就是,要真正輕而易舉的理解單例模式,JVM的類載入知識是不能少的,不然你只是會敲的層次,啥?不懂類載入?放心,宜春就是要你會,要你理解透徹。

  也不多嗶嗶了,直接擼碼走起…

  姿態一:餓漢式1(靜態變數)

  package singletonPattern;

  //餓漢式(靜態變數)

  class Singleton{

  //1、私有化構造方法,讓外部不能new

  private Singleton(){

  }

  //2、本類內部創建對象實例【靜態變數目的是為了類載入的時候創建實例】

  private final static Singleton instance=new Singleton();

  //3、提供一個公有的static靜態方法,返回實例對象

  public static Singleton getInstance(){

  return instance;

  }

  }

  //以下是測試代碼=====================

  public class SingletenDemo1 {

  public static void main(String[] args) {

  Singleton singleton=Singleton.getInstance();

  Singleton singleton2=Singleton.getInstance();

  //驗證一:

  System.out.println(singleton==singleton2);

  //驗證二:

  System.out.println(singleton.hashCode());

  System.out.println(singleton2.hashCode());

  }

  }

  //運行結果:

  // true

  // 460141958

  // 460141958

  /*

  餓漢式(靜態變數)方法

  優點:寫法簡單,在類載入的時候就完成了實例化,同時也就避免了線程同步問題,因此線程安全

  缺點:由於是在類載入時就完成了實例化,沒有達到懶載入的效果。如果一直沒有使用過這個實例,就造成了內存的浪費!

  總結:這種方式基於ClassLoader類載入機制避免了多線程的同步問題,只不過instance屬性在類載入就實例化,在單例模式中大多數都是調用getInstance方法,

  由於getInstance方法是static靜態的,調用它肯定會觸發類載入!但是觸發類載入的原因有很多,我們不能保證這個類會通過其他的方式觸發類載入(比如調用了其他的static方法)

  這個時候初始化instance就沒有達到lazy loading 懶載入的效果,可能造成內存的浪費!

  餓漢式(靜態變數)這種方式可以使用但是會造成內存的浪費!

  */

  姿態二:餓漢式2(static靜態代碼塊)

  package singletonPattern;

  //餓漢式2(static靜態代碼塊)

  class Singleton2{

  private Singleton2(){

  }

  private static Singleton2 instance;

  static{ //把創建單例對象的操作放進了static靜態代碼塊中==============

  instance = new Singleton2();

  }

  public static Singleton2 getInstance(){

  return instance;

  }

  }

  //餓漢式2(static靜態代碼塊)其實和第一種餓漢式(靜態變數)方法差不多,其優缺點一致!

  //唯一不同的就是把創建單例對象的操作放進了static靜態代碼塊中

  姿態三:懶漢式1(線程不安全)

  package singletonPattern;

  //懶漢式1(線程不安全)

  class Singleton3{

  private Singleton3(){

  }

  private static Singleton3 instance;

  public static Singleton3 getInstance(){

  if(instance == null){

  instance=new Singleton3();

  }

  return instance;

  }

  }

  /*

  懶漢式(線程不安全)的這種方式起到了懶載入的效果,但只能在單線程下使用。

  如果在多線程下,一個線程進入了if(singleton==null)判斷語句塊,還沒執行產生實例的句子,另一個線程

  又進來了,這時會產生多個實例,所以不安全。

  結語:懶漢式(線程不安全)在實際開發中,不要使用這種方式!!存在潛在危險

  */

  姿態四:懶漢式2(線程安全)

  package singletonPattern;

  //懶漢式2(線程安全)

  class Singleton4{

  private Singleton4(){

  }

  private static Singleton4 instance;

  public static synchronized Singleton4 getInstance(){

  if(instance == null){

  instance=new Singleton4();

  }

  return instance;

  }

  }

  /*

  懶漢式2(線程安全)方式

  優點:線程安全。

  缺點:效率太低,每次調用getInstance方法都要進行同步。

  結語:懶漢式2(線程安全)方式在開發中不推薦使用,主要是效率太低了*/

  姿態五:懶漢式3 同步代碼塊(線程安全)

  package singletonPattern;

  //懶漢式3 同步代碼塊(線程安全) 但是不滿足單例,在多線程下依舊會有多個實例

  class Singleton5{

  private Singleton5(){

  }

  private static Singleton5 instance;

  public static Singleton5 getInstance(){

  if(instance == null){ //多線程情況下可能多個線程進入這個if塊

  synchronized (Singleton5.class){ //到這裡只會一個一個創建實例,雖然安全,但是就不再是單例了

  instance=new Singleton5();

  }

  }

  return instance;

  }

  }

  /*

  懶漢式3 同步代碼塊(線程安全) 但是不滿足單例,依舊會有多個實例。

  結語:懶漢式3 同步代碼塊(線程安全)方式在開發中不使用 ,實際上這個單例設計的有點搞笑*/

  姿態六:雙重檢查單例

  package singletonPattern;

  //雙重檢查應用實例方式

  class Singleton6{

  private Singleton6(){}

  private static volatile Singleton6 singleton;

  public static Singleton6 getInstance(){

  if(singleton==null){

  synchronized(Singleton6.class){

  if(singleton == null){

  singleton= new Singleton6();

  }

  }

  }

  return singleton;

  }

  }

  /*

  雙重檢查應用實例方式:

  線程安全、延遲載入、效率較高

  結語:開發中推薦使用!

  */

  這個時候博主就得嗶嗶幾句了,細心的童鞋會發現有一個Volatile關鍵字,完了,沒見過,小白童鞋慌了!

  Volatile 變數具有 synchronized 的可見性特性,但是不具備原子特性。這就是說線程能夠自動發現 volatile 變數的最新值。

  這種實現方式既可以實現線程安全地創建實例,而又不會對性能造成太大的影響。它只是第一次創建實例的時候同步,以後就不需要同步了,從而加快了運行速度。

  姿態七:靜態內部類單例

  package singletonPattern;

  //static靜態內部類單例

  class Singleton7{

  private Singleton7(){}

  private static volatile Singleton7 instance;

  //寫一個static靜態內部類,給該類添加一個static靜態instance屬性

  private static class SingletonInstance{

  private static final Singleton7 SINGLETON_7=new Singleton7();

  }

  //

  public static synchronized Singleton7 getInstence(){

  return SingletonInstance.SINGLETON_7;

  }

  }

  /*

  靜態內部類單例方式

  1、這種方式採用了類載入機制來保證初始化實例時只有一個線程

  2、巧妙的將實例化Singleton操作放進getInstance方法中,getInstance方法返回靜態內部類中實例化好的Singleton

  3、類的靜態屬性只會在第一次載入類的時候初始化,也就是只會初始化一次,在這裡,JVM幫我們保證了線程的安全,類在初始化時,別的線程無法進入。

  優點:線程安全、利用靜態內部類特點實現延遲載入、效率高。

  開發中推薦使用這種靜態內部類單例方式!

  static靜態內部特點:

  1、外部類載入不會導致內部類載入,保證了其懶載入。

  */

  這個單例,宜春就不得不嗶嗶兩句了,要清楚這個單例,必須要明白static靜態內部特點,也就是外部類載入不會導致內部類載入!

  姿態八:枚舉

  package singletonPattern;

  //使用枚舉

  import com.sun.xml.internal.bind.v2.runtime.unmarshaller.XsiNilLoader;

  enum Singleton8{

  INSTANCE;

  public void methodName(){

  System.out.println("測試數據");

  }

  }

  /*

  枚舉方式的枚舉:

  推薦寫法,簡單高效。充分利用枚舉類的特性,只定義了一個實例,且枚舉類是天然支持多線程的。

  藉助JDK1.5中添加的枚舉來實現單例模式優點:

  1、不僅能避免多線程同步問題

  2、還能防止反序列化重新創建新的對象

  枚舉方式單例是由Effective java作者Josh Bloch提倡的,結語:推薦使用!

  */

  當然也可以測試一下

  public class SingletonDemo8 {

  public static void main(String[] args) {

  Singleton8 instance = Singleton8.INSTANCE;

  Singleton8 instance2 = Singleton8.INSTANCE;

  System.out.println(instance==instance2);

  System.out.println(instance.hashCode());

  System.out.println(instance2.hashCode());

  instance.methodName();

  }

  }

  運行結果:

  true

  460141958

  460141958

  測試數據

  屬實沒毛病!

  JDK源碼中單例模式的應用

  先來看一段Runtime 的源碼吧,並分析一下其使用的是種單例模式!

  /**

  * Every Java application has a single instance of class

  * Runtime that allows the application to interface with

  * the environment in which the application is running. The current

  * runtime can be obtained from the getRuntime method.

  *

  * An application cannot create its own instance of this class.

  *

  * @author unascribed

  * @see java.lang.Runtime#getRuntime()

  * @since JDK1.0

  */

  public class Runtime {

  private static Runtime currentRuntime = new Runtime();

  /**

  * Returns the runtime object associated with the current Java application.

  * Most of the methods of class Runtime are instance

  * methods and must be invoked with respect to the current runtime object.

  *

  * @return the Runtime object associated with the current

  * Java application.

  */

  public static Runtime getRuntime() {

  return currentRuntime;

  }

  /** Don't let anyone else instantiate this class */

  private Runtime() {}

  這應該不難看出吧!如果看不出的話只能說明你真的還沒有理解單例模式,我其實想說單例模式其實是23種設計模式中最簡單的一個,只是寫法比較多而已!同時面試官一般都會問單例模式,它已經是很基礎的了,問的稍微有點水平就是問你單例模式在JDK中哪裡運用到了,顯然JDK中的Runtime其實它使用的就是餓漢式單例!正如注釋所說,每一個java應用程序都有一個Runtime實例。Runtime的單例模式是採用餓漢模式創建的,意思是當你載入這個類文件時,這個實例就已經存在了。

  Runtime類可以取得JVM系統信息,或者使用gc()方法釋放掉垃圾空間,還可以使用此類運行本機的程序。

  還有就是spring Mvc 中的controller 默認是單例模式的,解析。

  單例模式總結:

  1、餓漢式(靜態變數)這種方式可以使用,但是沒有達到 lazy loading 懶載入的效果會造成內存的浪費!開發中不建議使用。

  2、餓漢式(static靜態代碼塊)其實和第一種餓漢式(靜態變數)方法差不多,其優缺點一致!唯一不同的就是把創建單例對象的操作放進了static靜態代碼塊中

  3、懶漢式(線程不安全)起到了懶載入的效果,但只能在單線程下使用。在實際開發中,不要使用這種方式!!!

  4、懶漢式2(線程安全)方式線程安全但是效率太低,每次調用getInstance方法都要進行同步。所以在開發中不推薦使用。

5、懶漢式3同步代碼塊(線程安全)方式在開發中不使用 ,實際上這個設計有點搞笑哈哈。

  6、雙重檢查應用實例方式,線程安全、延遲載入、效率較高。因此開發中推薦使用!

  7、靜態內部類單例方式線程安全、利用靜態內部類特點實現延遲載入、效率高。 開發中推薦使用這種靜態內部類單例方式!

  8、藉助JDK1.5中添加的枚舉來實現單例模式不僅能避免多線程同步問題還能防止反序列化重新創建新的對象。枚舉方式單例是由Effective java作者Josh Bloch提倡的,開發中推薦使用!

抱歉!評論已關閉.