1. 程式人生 > 其它 >android 單例模式_這 9 種單例模式你都會嗎?

android 單例模式_這 9 種單例模式你都會嗎?

技術標籤:android 單例模式

ed4f5912ee268d5fac6562d3281a8f7c.gif

身為程式設計師,你可能沒有系統的學習過設計模式,但是你一定知道單例模式,因為它相對簡單,而且最常被大家所用到。既然大家都用到過,也都知道為什麼我還要單獨列出一篇文章來寫呢?

因為絕大部分開發者平時對單例模式的認識,可能僅僅停留在“會用”的階段。為什麼會有這個模式?為什麼要用這個模式?在哪裡用單例模式最合適?亂用了會有什麼負面影響?

這些可能大多數人都一知半解。今天就讓我們大家一起來扒光單例模式的外衣,有深度的認識一下單例模式。

ef3924de86f8c20f88a497dec3934cce.gif

通過這篇文章你能學到什麼

(建議你可以帶著問題去學習)

  1. 單例模式的定義

  2. 單例模式在Android原始碼中的應用

  3. 單例模式的九種寫法以及優劣對比

  4. 單例模式的使用場景

  5. 單例模式存在的缺點

  6. 接下來我們就一起進入今天的學習了

單例模式的定義

在學單例模式之前,我想大家都會自己問自己:“單例模式存在的意義是什麼?我們為什麼要用單例模式?”

眾所周知,在古代封建社會,一個國家都只有一個國王或者叫皇帝。我們在這個國家的任何一個地方,只要提起國王,大家都知道他是誰。因為國王是唯一的。其實這個就是單例模式的核心思想:保證物件的唯一性。

單例模式(Singleton Pattern):確保某一個類只有一個例項,而且自行例項化並向整個系統提供這個例項,這個類稱為單例類,它提供全域性訪問的方法。 單例模式是一種物件建立型模式。

從其定義我們可以看出來單例模式存在三個要點:

1、例項唯一性

2、自行建立

3、全域性訪問

如何設計一個優秀的單例模式其實也是圍繞著這三點來的。

說了這麼多了,還不知道單例模式到底啥樣呢?接下來我們一起來著手設計這個“國王”的單例類。我們先看一下單例模式的類圖:

9f961b8dba50a835e88d6d36ce561e96.png

單例模式的類圖看起來很簡單,一個私有的當前型別的成員變數,一個私有的構造方法,一個 getInstance 方法,建立物件不再通過new 而通過 getInstance 讓該類自行建立。相信我們大多數人使用的單例模式都是這種,因為太簡單了。但是單例模式的寫法可不止這一種。接下來我們一起來看一下單例模式的九種寫法。

單例模式的九種寫法

一、餓漢式(靜態常量)

/**
* 餓漢式(靜態常量)
*/
class King {
private static final King kingInstance = new King;

static King getInstance {
return kingInstance;
}

private King {
}
}
  • 優點:這種寫法比較簡單,就是在類裝載的時候就完成例項化。避免了執行緒同步問題。

  • 缺點:在類裝載的時候就完成例項化,沒有達到Lazy Loading的效果。如果從始至終從未使用過這個例項,則會造成記憶體的浪費。

二、餓漢式(靜態程式碼塊)

/**
* 餓漢式(靜態程式碼塊)
*/
class King {
private static King kingInstance;

static {
kingInstance = new King;
}

private King {
}

public static King getKingInstance {
return kingInstance;
}
}
  • 優點:這種寫法比較簡單,就是在類裝載的時候就完成例項化。避免了執行緒同步問題。

  • 缺點:在類裝載的時候就完成例項化,沒有達到Lazy Loading的效果。如果從始至終從未使用過這個例項,則會造成記憶體的浪費。

三、懶漢式(執行緒不安全)

/**
* 懶漢式(執行緒不安全)
*/
public class King {
private static King kingInstance;

private King {
}

public static King getKingInstance {
if (kingInstance == ) {
kingInstance = new King;
}
return kingInstance;
}
}

優點:懶載入,只有使用的時候才會載入。

缺點:但是隻能在單執行緒下使用。如果在多執行緒下,一個執行緒進入了if (singleton == )判斷語句塊,還未來得及往下執行,另一個執行緒也通過了這個判斷語句,這時便會產生多個例項。所以在多執行緒環境下不可使用這種方式。

四、懶漢式(執行緒安全)

/**
* 懶漢式(執行緒安全,同步方法)
*/
public class King {
private static King kingInstance;

private King {
}

public static synchronized King getKingInstance {
if (kingInstance == ) {
kingInstance = new King;
}
return kingInstance;
}
}
  • 優點:懶載入,只有使用的時候才會載入,獲取單例方法加了同步鎖,保障執行緒安全。

  • 缺點:效率太低了,每個執行緒在想獲得類的例項時候,執行getInstance方法都要進行同步。

五、懶漢式(執行緒安全,同步程式碼塊)

/**
* 懶漢式(執行緒安全,同步程式碼塊)
*/
public class King {
private static King kingInstance;

private King {
}

public static King getKingInstance {
if (kingInstance == ) {
synchronized (King.class) {
kingInstance = new King;
}
}
return kingInstance;
}
}
  • 優點:改進了第四種效率低的問題。

  • 缺點:不能完全保證單例,假如一個執行緒進入了if (singleton == )判斷語句塊,還未來得及往下執行,另一個執行緒也通過了這個判斷語句,這時便會產生多個例項。

六、雙重檢查(DCL)

/**
* 雙重檢查(DCL)
*/
public class King {

private static volatile King kingInstance;

private King {
}

public static King getKingInstance {
if (kingInstance == ) {
synchronized (King.class) {
if (kingInstance == ){
kingInstance = new King;
}
}
}
return kingInstance;
}
}
  • 優點:執行緒安全;延遲載入;效率較高。

  • 缺點:JDK < 1.5 的時候不可用

  • 不可用原因:由於volatile關鍵字會遮蔽Java虛擬機器所做的一些程式碼優化,可能會導致系統執行效率降低,而JDK 1.5 以及之後的版本都修復了這個問題。(面試裝逼用,謹記!!!)

982092a5d4fab70e6f2cf7da18150a34.gif

七、靜態內部類

/**
* 靜態內部類
*/
public class King {

private King {
}

private static class KingInstance{
private static final King KINGINSTANCE = new King;
}

public static King getInstance{
return KingInstance.KINGINSTANCE;
}
}
  • 優點:避免了執行緒不安全,延遲載入,效率高。

  • 缺點:暫無,最推薦使用。

  • 特點:這種方式跟餓漢式方式採用的機制類似,但又有不同。

  • 兩者都是採用了類裝載的機制來保證初始化例項時只有一個執行緒。不同的地方在餓漢式方式是隻要Singleton類被裝載就會例項化,沒有Lazy-Loading的作用,而靜態內部類方式在Singleton類被裝載時並不會立即例項化,而是在需要例項化時,呼叫getInstance方法,才會裝載SingletonInstance類,從而完成Singleton的例項化。 類的靜態屬性只會在第一次載入類的時候初始化,所以在這裡,JVM幫助我們保證了執行緒的安全性,在類進行初始化時,別的執行緒是無法進入的。

八、列舉

/**
* 列舉
*/
public enum King {
KINGINSTANCE;
}
  • 優點:不僅能避免多執行緒同步問題,而且還能防止反序列化重新建立新的物件。

  • 缺點:JDK 1.5之後才能使用。

九、容器類管理

/**
* 使用容器實現單例模式(可以用於管理單例,有興趣的可以嘗試一下)
* */
class InstanceManager {
private static Map objectMap = new HashMap<>;
private InstanceManager{}
public static void registerService(String key,Object instance){
if (!objectMap.containsKey(key)){
objectMap.put(key,instance);
}
}
public static Object getService(String key){
return objectMap.get(key);
}
}
/**
* 使用方式
* Dog類就不貼出來了
* 自己隨便寫個就行
* 可以執行一下看看 列印的地址是否一致
*/
class Test {
public static void main(String[] args) {

InstanceManager .registerService("dog