Android設計模式之——單例模式

一、介紹

單例模式是應用最廣的模式之一,也可能是很多初級工程師唯一會使用的設計模式。在應用這個模式時,單例對象的類必須保證只有一個實例存在。許多時候整個系統只需要擁有一個全局對象,這樣有利于我們協調系統整體的行為。

二、定義

確保某一個類只有一個實例,而且自行實例化并向整個系統提供這個實例。

三、使用場景

確保某個類有且只有一個對象的場景,避免產生多個對象消耗過多的資源,或者某種類型的對象只應該有且只有一個。例如,創建一個對象需要消耗的資源過多,如要訪問IO和數據庫等資源,這時就要考慮使用單例模式。

四、實現方式

1、餓漢模式

示例代碼:

/*** 餓漢模式*/
public class Singleton {private static Singleton instance;private Singleton(){}public static Singleton getInstance(){if(instance == null){instance = new Singleton();}return instance;}
}

優點:延遲加載(需要的時候才去加載)

缺點:線程不安全,在多線程中很容易出現不同步的情況,如在數據庫對象進行的頻繁讀寫操作時。

2、懶漢模式

示例代碼:

/*** 懶漢模式*/
public class Singleton {private static Singleton instance;private Singleton(){}public static synchronized Singleton getInstance(){if(instance == null){instance = new Singleton();}return instance;}
}

與餓漢模式相比,getInstance()方法中添加了synchronized關鍵字,也就是說getInstance是一個同步方法,這就在多線程的情況下保證單例對象的唯一性的手段。但是,細想一下,大家可能會發現一個問題,即使instance已經被初始化(第一次調用的時候就會被初始化instance),每次調用getInstance方法都會進行同步,這樣會消耗不必要的資源,這也是懶漢模式存在的最大問題。

優點:解決了線程不安全的問題。

缺點:第一次加載時需要及時進行實例化,反應稍慢,最大問題是每次調用getInstance都進行同步,造成不必要的同步開銷。

補充:在Android源碼中使用的該單例方法有:InputMethodManager,AccessibilityManager等都是使用這種單例模式

3、Double Check Lock(DCL)雙重檢查鎖定

示例代碼:

/*** 雙重檢查鎖定(DCL)單例模式*/
public class Singleton {private static Singleton instance;private Singleton(){}public static Singleton getInstance(){if(instance == null){synchronized (Singleton.class) {if(instance == null) {instance = new Singleton();}}}return instance;}
}

本程序的亮點自然在getInstance方法上,可以看到getInstance方法中對instance進行了兩次判空:第一層判斷主要是為了避免不必要的同步,第二層的判斷則是為了在null的情況下創建實例。

假設線程A執行到instance = new Singleton()語句,這里看起來是一句代碼,但實際上它并不是一個原子操作,這句代碼最終會被編譯成多條匯編指令,它大致做了3件事情:

(1)個Singleton的實例分配內存;

(2)調用Singleton()的構造函數,初始化成員字段;

(3)將instance對象指向分配的內存空間(此時instance就不是null了)。

但是,由于java編譯器允許處理器亂序執行,以及JDK1.5之前JMM(Java Memory Model,即Java內存模型)中Cache、寄存器到主內存回寫順序的規定,上面的第二和第三句的順序是無法保證的。也就是說,執行順序可能是1-2-3也可能是1-3-2。如果是后者,并且在3執行完畢、2未執行之前,被切換到線程B上,這時候instance因為已經在線程A內執行過了第三點,instance已經是非空了,所有,線程B直接取走了instance,再使用時就會出錯,這就是DCL失效問題,而且這種難以跟蹤難以重現的錯誤很可能會隱藏很久。

在JDK1.5之后,SUN官方已經注意到這種問題,調整了JVM,具體化了volatile關鍵字,因此,如果JDK是1.5或之后的版本,只需要將instance的定義改成private volatile static Singleton instance就可以保證instance對象每次都是從主內存中讀取,就可以使用DCL的寫法來完成單例模式。當然,volatile或多或少也會影響到性能,但考慮到程序的正確性,犧牲這點性能還是值得的。

優點:資源利用率高,第一次執行getInstance時單例對象才會被實例化,效率高。在并發量不多,安全性不高的情況下或許能很完美運行單例模式

缺點:第一次加載時反應稍慢,也由于Java內存模型的原因偶爾會失敗。在高并發環境下也有一定的缺陷,雖然發生概率很小。

補充:在android圖像開源項目Android-Universal-Image-Loader (https://github.com/nostra13/Android-Universal-Image-Loader)中使用的是這種方式。

DCL模式是使用最多的單例實現方式,它能夠在需要時才實例化單例對象,并且能夠在絕大多數場景下保證單例對象的唯一性,除非你的代碼在并發場景比較復雜或者低于JDK6版本下使用,否則,這種方式一般能夠滿足需要。

4、靜態內部類單例模式

DCL雖然在一定程度上解決了資源消耗、多余的同步、線程安全等問題,但是,它還是在某些情況下出現失效的問題。這個問題被稱為雙重檢查鎖定(DCL)失效,在《Java并發編程實踐》一書的最后談到了這個問題,并指出這種“優化”是丑陋的,不贊成使用。而建議使用如下的代碼替代:

示例代碼:

/*** 靜態內部類單例模式*/
public class Singleton {private Singleton(){}public static Singleton getInstance(){return SingletonHolder.instance;}/*** 靜態內部類* 延遲加載,減少內存開銷 */private static class SingletonHolder{private static final Singleton instance = new Singleton();}
}

當第一次加載Singleton類時并不會初始化instance,只有在第一次調用Singleton的getInstance方法時才會導致instance被初始化。因此,第一次調用getInstance方法會導致虛擬機加載SingletonHolder類,這種方式不僅能夠確保線程安全,也能夠保證單例對象的唯一性,同時也延遲了單例的實例化,所以這是推薦使用的單例模式實現方式。

優點:延遲加載,線程安全(java中class加載時互斥的),也減少了內存消耗

5、枚舉單例

前面講解了一些單例模式實現方式,但是,這些實現方式不是稍顯麻煩就是會在某些情況下出現問題。

示例代碼:

/*** 枚舉單例模式*/
public enum Singleton {  /** * 1.從Java1.5開始支持; * 2.無償提供序列化機制; * 3.絕對防止多次實例化,即使在面對復雜的序列化或者反射攻擊的時候; */  instance;  private String others;  Singleton() {  }  public String getOthers() {  return others;  }  public void setOthers(String others) {  this.others = others;  }  
} 

寫法簡單是枚舉單例最大的優點,枚舉在Java中與普通的類是一樣的,不僅能夠有字段,還能有自己的方法。最重要的是默認枚舉實例的創建是線程安全的,并且在任何情況下它都是一個單例。

為什么這么說呢?在上述的幾種單例模式實現中,在一個情況下它們會出現重新創建對象的情況,那就是反序列化。

通過序列化可以將一個單例的實例對象寫到磁盤,然后在讀回來,從而有效的獲得一個實例。即使構造函數是私有的,反序列化時依然可以通過特殊的途徑去創建類的一個新的實例,相當于調用該類的構造函數。反序列化操作提供了一個特別的鉤子函數,類中具有一個私有的、被實例化的方法readResolve(),這個方法可以讓開發人員控制對象的反序列化。例如,上述幾個示例中如果要杜絕單例對象在被反序列化時重新生成對象,那么必須加入如下方法:

private Object readResolve() throws ObjectStreamException {return instance;
}

也就是在readResolve方法中將instance對象返回,而不是默認的重新生成一個新的對象。而對于枚舉,并不存在這個問題,因為即使反序列化它也不會重新生成新的實例。

優點:無償提供序列化機制,絕對防止多次實例化,即使在面對復雜的序列化或者反射攻擊的時候。

缺點:從Java1.5開始支持。

上面主要講了單例模式5種創建方法,大家可以根據其優缺點進行個人實際項目中的使用。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/447037.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/447037.shtml
英文地址,請注明出處:http://en.pswp.cn/news/447037.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

我的職業生涯規劃(軟件工程)

以后筆記先在語雀整理 方便一點https://www.yuque.com/juhao-pqdor/goeie3 整理一下自己的筆記 彌補一下以前沒寫博客的遺憾吧 二十載求學路將盡,行文至此,思緒萬千。求學之路始于家鄉,竿轉熱河,而今終于石門。一路行之如人飲水…

C++ primer第六章6.5函數的學習 之特殊用途的語言特性

6.5.1 默認實參 將反復出現的數值稱為函數的默認實參,調用含有默認實參的時候可以包含該實參也可以不包含比如程序打開頁面會有一個默認的寬高,如果用戶不喜歡也允許用戶自由指定與默認數值不同的數值,具體例子如下圖所示 typedef string::s…

Android設計模式之——Builder模式

一、介紹 Builder模式是一步一步創建一個復雜對象的創建型模式,它允許用戶在不知道內部構建細節的情況下,可以更精細的控制對象的構造流程。該模式是為了將構建復雜對象的過程和它的部件解耦,使得構建過程和部件的表示隔離開來。 因為一個復…

c++后端開發書籍推薦

推薦書籍: 略讀80% 精讀50% C: C Primer Plus C和指針(入門書 不只是指針) C陷阱與缺陷(宏相關) C專家編程 C: 有專門的視頻 C primer C程序設計原理與實踐(c之父寫的 入門經典) Ef…

C++ primer第六章6.6函數匹配

函數的匹配 當重載函數的形參數量相等以及某些形參的類型可以由其他的類型轉化得來的時候,對于函數的匹配就會變得很難 確定候選函數和可行函數 函數匹配的第一步就是選定本次調用對應的重載函數集,集合中的函數稱為候選函數。候選函數具有兩個特征&am…

Android設計模式之——原型模式

一、介紹 原型模式是一個創建型的模式。原型二字表明了該模型應該有一個樣板實例,用戶從這個樣板對象中復制出一個內部屬性一致的對象,這個過程也就是我們俗稱的“克隆”。被復制的實例就是我們所稱的“原型”,這個原型也是可定制的。原型模…

C++ primer第六章6.7函數指針

函數指針 函數指針指向的是函數而不是對象。和其他指針一樣,函數指針指向某種特定的類型。函數的類型由他的返回類型和形參類型共同決定,而與函數的名字無關。 //比較兩個string對象的長度 bool lengthCompare(const string &,const string &);…

Android設計模式之——工廠方法模式

一、介紹 工廠方法模式(Factory Pattern),是創建型設計模式之一。工廠方法模式是一種結構簡單的模式,其在我們平時開發中應用很廣泛,也許你并不知道,但是你已經使用了無數次該模式了,如Android…

C++ primer第十八章 18.1小結 異常處理

18.1 異常處理 異常處理機制,允許程序獨立開發的部分能夠在運行的時候出現的問題進行通信并且做出相應的處理,異常的處理使得我們可以將問題的檢測和處理分離開來。程序的一部分負責檢測問題的出現,然后將解決這個問題的任務傳遞給程序的另一…

淺談equals與==

一、前言 示例代碼: public static void main(String[] args) throws IOException {String str1 new String("hello");String str2 new String("hello");String str3 "cde";String str4 "cde";int i1 3;int i2 3;In…

針對C++異常的學習

源碼 頭文件 sdf_exception.h #pragma once#include <exception> #include <string>namespace sdf {namespace common{using sdf_error_code_t uint32_t;class SdfException : std::exception{public:explicit SdfException(sdf_error_code_t errorCode) : erro…

Android設計模式之——抽象工廠模式

一、介紹 抽象工廠模式&#xff08;Abstract Factory Pattern&#xff09;&#xff0c;也是創建型設計模式之一。前一節我們已經了解了工廠方法模式&#xff0c;那么這個抽象工廠又是怎么一回事呢&#xff1f;大家聯想一下現實生活中的工廠肯定都是具體的&#xff0c;也就是說…

Android設計模式之——策略模式

一、介紹 在軟件開發中也常常遇到這樣的情況&#xff1a;實現某一個功能可以有多種算法或者策略&#xff0c;我們根據實際情況選擇不同的算法或者策略來完成該功能。例如&#xff0c;排序算法&#xff0c;可以使用插入排序、歸并排序、冒泡排序等。 針對這種情況&#xff0c;…

密碼學在區塊鏈隱私保護中的應用學習

身份隱私保護技術 混淆服務 混淆服務的目的在于混淆消息雙方的聯系&#xff08;如 圖 2 所示&#xff09;。當發送方需要告知接收方消息 M 時&#xff0c; 它會首先用接收方的公鑰 KB 加密 M&#xff0c;并在密文后 附帶真實接收地址 R。為了借助第三方&#xff08;圖 2 中的…

值類型和引用類型的區別

一、定義 引用類型表示你操作的數據是同一個&#xff0c;也就是說當你傳一個參數給另一個方法時&#xff0c;你在另一個方法中改變這個變量的值&#xff0c;那么調用這個方法是傳入的變量的值也將改變。 值類型表示復制一個當前變量傳給方法&#xff0c;當你在這個方法中改變…

面向區塊鏈的高效物化視圖維護和可信查詢論文學習

物化視圖介紹 如何維護物化視圖仍舊是一個開放問題.在關系數據庫中,增量刷新的物化視圖維護策略可劃分為立即維護和延遲維護兩大類.立即維護策略的優點是實現較為簡單,在單數據源下不 存在一致性問題;然而該策略將物化視圖維護過程嵌入到更新事務之中,延長了更新事務的提交時間…

Java基礎知識(一)

一、接口 類描述了一個實體&#xff0c;包括實體的狀態&#xff0c;也包括實體可能發出的動作。 接口定義了一個實體可能發出的動作。但是只是定義了這些動作的原型&#xff0c;沒有實現&#xff0c;也沒有任何狀態信息。 所以接口有點象一個規范、一個協議&#xff0c;是一個…

密碼學數字信封的介紹

對稱密碼和非對稱密碼 對稱密碼&#xff1a;加解密運算非常快&#xff0c;適合處理大批量數據&#xff0c;但其密碼的分發與管理比較復雜非對稱密碼&#xff1a;公鑰和私鑰分離&#xff0c;非常適合密鑰的分發和管理 數字信封的定義 如果將對稱密碼算法和非對稱密碼算法的優點…

Android設計模式之——狀態模式

一、介紹 狀態模式中的行為是由狀態來決定的&#xff0c;不同的狀態下有不同的行為。狀態模式和策略模式的結構幾乎完全一樣&#xff0c;但它們的目的、本質卻完全不一樣。狀態模式的行為是平行的、不可替換的&#xff0c;策略模式的行為是彼此獨立、可相互替換的。用一句話來…

Android設計模式之——責任鏈模式

一、介紹 責任鏈模式&#xff08;Iterator Pattern&#xff09;&#xff0c;是行為型設計模式之一。什么是”鏈“&#xff1f;我們將多個節點首尾相連所構成的模型稱為鏈&#xff0c;比如生活中常見的鎖鏈&#xff0c;就是由一個個圓角長方形的鐵環串起來的結構。對于鏈式結構…