java設計模式其一 單例模式

ckxllf發表於2020-03-18

  單例模式

  今天給大家帶來的是23種設計模式的第一種——單例模式。

  一、引言

  單例模式是啥?故名思意,就是單著的意思。沒錯,就是為了來保證整個系統執行中,從頭至尾只有一個物件。比如說,我們最可愛的學校,可以有很多學生,可以有很多主任,但是不能有很多校長。為什麼?因為要確保只有一個校長,學校這個系統才不會因為受干擾崩潰,所以單例模式應運而生。

  二、實現方式

  都知道了單例模式是幹嘛的了,那就好辦了。首先你要確保整個系統的laowang類只有老王一個物件 最重要的前提你要做什麼??可想而知,老王不能被其他類所創造出來啊。

  因此有如下做法:

  1. 先把構造方法給私有化了(private)。

  2. 接著在程式執行的時候建立一個物件放在記憶體裡就得了。

  你沒看錯,要實現單例模式,確確實實就只有這兩步,第一步2秒鐘搞定,第二步就是我們要來探討的部分了。

  實現單例模式有五種做法:

  餓漢式:

  也就是在程式裝載時提前把物件建立了,有人來就給他。

  懶漢式:

  在有人需要的時候,再建立第一個物件,然後再給他。(懶載入)

  雙重檢驗方式:

  內部類方式:

  列舉方式:

  提示:在上面實現方式中只展現執行緒安全的做法,詳細的我後面會指出。

  三、具體實現

  1. 餓漢式

  分為兩步走:

  把構造方法私有化

  在程式裝載時提前建立好例項

  class Laowang{

  private Laowang(){}//私有化構造方法

  private static Laowang laowang=new Laowang();//直接建立靜態例項

  //對外提供靜態方法獲取當前的Laowang

  public static Laowang getLaowang(){

  return laowang;

  }

  }

  //Main方法

  public static void main(String[] args) {

  //用Laowang類的靜態方法getLaowang()獲取例項;

  Laowang laowang1=Laowang.getLaowang();

  Laowang laowang2=Laowang.getLaowang();

  //判斷laowang1是否和老王2是同一個物件(是輸出true,否則false)

  System.out.println(laowang1==laowang2);

  }

  執行結果: true

  上面這個例子中,在老王這個類中,先私有化構造方法,接著建立一個靜態屬性laowang, 然後提供一個對外的靜態方法getLaowang()可以給別人拿這個laowang。(因為你已經把構造方法私有化了,所以你只能透過靜態方法把laowang給別人。)

  Main方法中定義了2個引用laowang1,laowang2,但是都是透過同一種方式拿到例項物件laowang.因此拿到的是同一個物件,所以返回true,這就是餓漢式實現法。

  優點:實現簡單,執行緒安全。

  缺點:很明顯,在類裝載的時候直接建立,有時候你不需要它,它也會創 建,造成記憶體資源浪費。

  餓漢式也有另外一種寫法,也是一樣的效果。把new Laowang()放在靜態程式碼塊裡,如下:

  class Laowang{

  private static Laowang laowang;

  private Laowang(){}//私有化構造方法

  static {

  laowang=new Laowang();

  }

  //對外提供靜態方法獲取當前的Laowang

  public static Laowang getLaowang(){

  return laowang;

  }

  }

  2. 懶漢式

  在程式需要用到呼叫的時候才給它(懶載入),因此做法如下:

  class Laowang{

  private static Laowang laowang;

  private Laowang(){}//私有化構造方法

  //對外提供靜態方法,建立例項然後返回,當前的Laowang

  public static synchronized Laowang getLaowang(){

  if (laowang == null) {

  laowang=new Laowang();

  }

  return laowang;

  }

  }

  此做法需要在方法宣告加上synchronized,(具體作用:比如說很多人來訪問這個方法,他們必須排隊訪問) 這種怎麼理解呢?就是說在別人需要用到laowang,呼叫getLaowang()的時候,先排隊,排到他的時候,進去判斷laowang是不是為空,是就new一個,不是就拿當前laowang給他。當然main執行結果還是為true這裡就不作多的描述。

  優點: 不會造成記憶體浪費

  缺點: 很明顯,人人平等,大家都要排隊,既然排隊就慢,高併發情況下,極度影響效率。

  在這裡解釋為什麼要加同步鎖:如果不加的話,舉個例子,程式執行剛開始,小黑和小紅同時訪問這個方法,同時作判斷,肯定同時都判斷為空,而且兩個人都進去了,new Laowang();很明顯直接造成laowang不是單例的了。因此要加鎖。 小黑小紅都要排隊。

  3. 雙重檢驗鎖

  也是屬於懶載入

  class Laowang{ 鄭州哪個人流醫院好

  private volatile static Laowang laowang;//必須加上volatile 關鍵字

  private Laowang(){}//私有化構造方法

  //對外提供靜態方法,建立例項然後返回,當前的Laowang

  public static Laowang getLaowang(){

  if (laowang == null) {

  synchronized (Laowang.class){ //同步程式碼(照樣要排隊)

  if (laowang==null){

  laowang=new Laowang();

  }

  }

  }

  return laowang;

  }

  }

  分析一下程式碼哈,首先靜態屬性laowang要加上volatile (具體作用要詳細瞭解的話建議百度搜一下哈,屬於多執行緒內容的一部分)。然後再getLaowang()方法中,先判斷laowang是否為空,如果為空,請排隊。排完隊後,再次判斷,如果還是為空,才new一個返回。

  舉個例子解釋一下為什麼要這樣做:

  還是小黑小紅,同時併發進來訪問,然後肯定同時第一次判斷都為空,接著兩個人排隊,小黑先進去玩會,肯定第二次判斷為空,結果肯定是小黑new了一個laowang走了。排到小紅了,小紅進來第二次判斷發現laowang不為空了,直接帶走。

  溫馨提示:再看一邊再繼續看下面內容

  這個時候有人問了,那為什麼要第一次判斷幹嘛,直接排隊他不香嗎?沒錯,我第一次也這不理解的地方。我們腦回路回退到小黑剛new完laowang走了。剛要排到小紅了。突然來了個第三者小三,如果你沒有第一次判斷,小三還要繼續排在小紅後面,造成效率降低。但是現在小三第一次判斷發現laowang已經不為空了(此時laowang是第一個人小黑弄出來的),直接帶走。

  優點: 解決了排隊效率降低的問題,執行緒安全。

  缺點: 實現較為複雜。

  4. 內部類方式

  也是屬於懶載入,故名思意,首先整個內部類出來,程式碼如下:

  兄臺要看如下程式碼,請先了解final關鍵字的作用

  這裡對final作簡單描述:

  對類使用:表示該類不能被繼承(俗稱斷子絕孫類)

  對方法使用:表示該方法不能被重寫

  對基礎型別使用:比如說int,float…表示該值不可以被更改

  對引用物件使用:表示該引用從頭到尾只指向一個物件

  以上3.對基礎型別使用,4.對引用物件使用都必須直接賦值。

  class Laowang{

  private Laowang(){} //私有化構造方法

  //對外提供靜態方法,呼叫內部類的屬性,返回

  public static final Laowang getLaowang(){

  return laowangHolder.INSTANCE;

  }

  //靜態內部類

  private static final class LaowangHolder{

  private static final Laowang INSTANCE =new Laowang();

  }

  }

  解釋以上程式碼:首先宣告瞭一個內部類(LaowangHolder),他有個靜態且被final修飾的屬性INSTANCE,因此需要直接賦值,new Laowang();接著在getLaowang()方法中呼叫內部類的INSTANCE屬性,返回。因為INSTANCE被final修飾,只指向同一個laowang,所以他是單例的。

  5. 列舉方式

  這些方法實現相對簡單,所以直接上程式碼:

  enum Laowang{

  laowang;

  public void whateverMethod(){}

  }

  //Main方法:

  public static void main(String[] args) {

  //直接當成屬性呼叫就可以了

  Laowang laowang1=Laowang.laowang;

  Laowang laowang2=Laowang.laowang;

  System.out.println(laowang1==laowang2);

  }

  直接宣告一個列舉類,定義一個屬性,main方法中直接獲取即可。

  四、總結

  單例模式執行緒安全的就這幾種了,反正我自己是搞懂了,希望你們也能看懂。想看對單例瞭解更深入可以點我下方的連結。看有經驗的人說,單例是最常用的也是最簡單的一種設計模式之一,在工作中實現方式大都選擇餓漢式,或者內部類,列舉, 但是懶漢,雙重校驗鎖就比較少了。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69945560/viewspace-2681089/,如需轉載,請註明出處,否則將追究法律責任。

相關文章