單例模式 Singleton 簡單實例設計模式解析
前言
今天我來全面總結一下Android開發中最常用的設計模式 - 單例模式。
關于設計模式的介紹,可以看下我之前寫的:1分鐘全面了解“設計模式”
目錄

1. 引入
1.1 解決的是什么問題
之前說過,設計模式 = 某類特定問題的解決方案,那么單例模式是解決什么問題的解決方案呢?
含義:單例 =一個實例;
解決的問題:降低對象之間的耦合度
解決方法:單例模式,即實現一個類只有一個實例化對象,并提供一個全局訪問點 
1.2 實例引入
接下來我用一個實例來對單例模式進行引入
背景:小成有一個塑料生產廠,但里面只有一個倉庫。
目的:想用代碼來實現倉庫的管理
現有做法: 建立倉庫類和工人類 
其中,倉庫類里的quantity=商品數量;工人類里有搬運方法MoveIn(int i)和MoveOut(int i)。
出現的問題:通過測試發現,每次工人搬運操作都會新建一個倉庫,就是貨物都不是放在同一倉庫,這是怎么回事呢?(看下面代碼)
package scut.designmodel.SingletonPattern;//倉庫類class StoreHouse {  private int quantity = 100;  public void setQuantity(int quantity) {    this.quantity = quantity;  }  public int getQuantity() {    return quantity;  }}//搬貨工人類class Carrier{  public StoreHouse mStoreHouse;  public Carrier(StoreHouse storeHouse){    mStoreHouse = storeHouse;  }  //搬貨進倉庫  public void MoveIn(int i){    mStoreHouse.setQuantity(mStoreHouse.getQuantity()+i);  }  //搬貨出倉庫  public void MoveOut(int i){    mStoreHouse.setQuantity(mStoreHouse.getQuantity()-i);  }}//工人搬運測試public class SinglePattern {  public static void main(String[] args){    StoreHouse mStoreHouse1 = new StoreHouse();    StoreHouse mStoreHouse2 = new StoreHouse();    Carrier Carrier1 = new Carrier(mStoreHouse1);    Carrier Carrier2 = new Carrier(mStoreHouse2);    System.out.println("兩個是不是同一個?");    if(mStoreHouse1.equals(mStoreHouse2)){//這里用equals而不是用 == 符號,因為 == 符號只是比較兩個對象的地址      System.out.println("是同一個");    }else {      System.out.println("不是同一個");    }    //搬運工搬完貨物之后出來匯報倉庫商品數量    Carrier1.MoveIn(30);    System.out.println("倉庫商品余量:"+Carrier1.mStoreHouse.getQuantity());    Carrier2.MoveOut(50);    System.out.println("倉庫商品余量:"+Carrier2.mStoreHouse.getQuantity());  }}結果:
兩個是不是同一個?不是同一個倉庫商品余量:130倉庫商品余量:50
2. 單例模式介紹
2.1 解決的問題(應用場景)
沖突:從上面的結果可以看出,工人類操作的明顯不是同一個倉庫實例。
目標:全部工人操作的是同一個倉庫實例
單例模式就是為了解決這類問題的解決方案:實現一個類只有一個實例化對象,并提供一個全局訪問點2.2 工作原理
在Java中,我們通過使用對象(類實例化后)來操作這些類,類實例化是通過它的構造方法進行的,要是想實現一個類只有一個實例化對象,就要對類的構造方法下功夫: 
單例模式的一般實現:(含使用步驟)
public class Singleton {//1. 創建私有變量 ourInstance(用以記錄 Singleton 的唯一實例)//2. 內部進行實例化  private static Singleton ourInstance = new Singleton();//3. 把類的構造方法私有化,不讓外部調用構造方法實例化  private Singleton() {  }//4. 定義公有方法提供該類的全局唯一訪問點//5. 外部通過調用getInstance()方法來返回唯一的實例  public static Singleton newInstance() {    return ourInstance;  }}好了,單例模式的介紹和原理應該了解了吧?那么我們現在來解決上面小成出現的“倉庫不是一個”的問題吧!
2.3 實例介紹
小成使用單例模式改善上面例子的代碼:
package scut.designmodel.SingletonPattern;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;//單例倉庫類class StoreHouse {  //倉庫商品數量  private int quantity = 100;  //自己在內部實例化  private static StoreHouse ourInstance = new StoreHouse();;  //讓外部通過調用getInstance()方法來返回唯一的實例。  public static StoreHouse getInstance() {    return ourInstance;  }  //封閉構造函數  private StoreHouse() {  }  public void setQuantity(int quantity) {    this.quantity = quantity;  }  public int getQuantity() {    return quantity;  }}//搬貨工人類class Carrier{  public StoreHouse mStoreHouse;  public Carrier(StoreHouse storeHouse){    mStoreHouse = storeHouse;  }  //搬貨進倉庫  public void MoveIn(int i){    mStoreHouse.setQuantity(mStoreHouse.getQuantity()+i);  }  //搬貨出倉庫  public void MoveOut(int i){    mStoreHouse.setQuantity(mStoreHouse.getQuantity()-i);  }}//工人搬運測試public class SinglePattern {  public static void main(String[] args){    StoreHouse mStoreHouse1 = StoreHouse.getInstance();    StoreHouse mStoreHouse2 = StoreHouse.getInstance();    Carrier Carrier1 = new Carrier(mStoreHouse1);    Carrier Carrier2 = new Carrier(mStoreHouse2);    System.out.println("兩個是不是同一個?");    if(mStoreHouse1.equals(mStoreHouse2)){      System.out.println("是同一個");    }else {      System.out.println("不是同一個");    }    //搬運工搬完貨物之后出來匯報倉庫商品數量    Carrier1.MoveIn(30);    System.out.println("倉庫商品余量:"+Carrier1.mStoreHouse.getQuantity());    Carrier2.MoveOut(50);    System.out.println("倉庫商品余量:"+Carrier2.mStoreHouse.getQuantity());  }}結果:
兩個是不是同一個?是同一個倉庫商品余量:130倉庫商品余量:80
從結果分析,使用了單例模式后,倉庫類就只有一個倉庫實例了,再也不用擔心搬運工人進錯倉庫了!!!
2.4 優點
2.5 缺點
3. 單例模式的實現方式
3.1 一般情況
餓漢式(最簡單的單例實現方式)
class Singleton {  private static Singleton ourInstance = new Singleton();  private Singleton() {  }  public static Singleton newInstance() {    return ourInstance;  }}應用場景:
懶漢式
懶漢式與餓漢式最大的區別是單例的初始化操作的時機:
class Singleton {  private static Singleton ourInstance = null;  private Singleton() {  }  public static Singleton newInstance() {  if( ourInstance == null){    ourInstance = new Singleton();    }    return ourInstance;  }}應用場景:
3.2 多線程下的單例模式實現
在多線程的情況下:
解決方案1:同步鎖
使用同步鎖 synchronized (Singleton.class) 防止多線程同時進入造成instance被多次實例化。
class Singleton {  private static Singleton ourInstance = null;  private Singleton() {  }  public static Singleton newInstance() {   synchronized (Singleton.class){     if( ourInstance == null){      ourInstance = new Singleton();    }   }    return ourInstance;  }}解決方案2:雙重校驗鎖
在同步鎖的基礎上( synchronized (Singleton.class) 外)添加了一層if,這是為了在Instance已經實例化后下次進入不必執行 synchronized (Singleton.class) 獲取對象鎖,從而提高性能。
class Singleton {  private static Singleton ourInstance = null;  private Singleton() {  }  public static Singleton newInstance() {if( ourInstance == null){ synchronized (Singleton.class){   if( ourInstance == null){     ourInstance = new Singleton();     }   } }    return ourInstance;  }}解決方案3:靜態內部類
在JVM進行類加載的時候會保證數據是同步的,我們采用內部類實現:在內部類里面去創建對象實例。 
只要應用中不使用內部類 JVM 就不會去加載這個單例類,也就不會創建單例對象,從而實現“懶漢式”的延遲加載和線程安全。
class Singleton {  //在裝載該內部類時才會去創建單例對象  private static class Singleton2{   private static Singleton ourInstance = new Singleton();  }  private Singleton() {  }  public static Singleton newInstance() {    return Singleton2.ourInstance;  }}解決方案4:枚舉類型
最簡潔、易用的單例實現方式,(《Effective Java》推薦)
public enum Singleton{  //定義一個枚舉的元素,它就是Singleton的一個實例  instance;  public void doSomething(){  }  }使用方式如下:
Singleton singleton = Singleton.instance;singleton.doSomething();
5. 總結
本文主要對單例模式進行了全面介紹,包括原理和實現方式,接下來我會繼續講解其他設計模式,有興趣可以繼續關注
感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!
新聞熱點
疑難解答