目錄
- 定義
- 使用場(chǎng)景
- UML類圖
- 實(shí)現(xiàn)方式
- 餓漢式
- 懶漢式
- Double Check LockDCL雙重檢查鎖
- 靜態(tài)內(nèi)部類
- 枚舉單例
- 使用容器實(shí)現(xiàn)單例
- Android源碼中的單例模式
- 總結(jié)
- 優(yōu)點(diǎn)
- 缺點(diǎn)
博客地址
最近在看《Android源碼設(shè)計(jì)模式解析與實(shí)戰(zhàn)》這本書,發(fā)現(xiàn)里面還有對(duì)源碼的一些分析,之前也沒好好看過設(shè)計(jì)模式,就來做個(gè)筆記,跟著看一下。
包括設(shè)計(jì)模式和一些源碼分析。
定義
確保某一個(gè)類只有一個(gè)實(shí)例,而且自行實(shí)例化并向整個(gè)系統(tǒng)提供這個(gè)實(shí)例。
使用場(chǎng)景
需要確保一個(gè)類只有一個(gè)實(shí)例的場(chǎng)景,避免產(chǎn)生多個(gè)對(duì)象小號(hào)過多的資源,或者是這個(gè)類只應(yīng)該有一個(gè)實(shí)例。比如創(chuàng)建一個(gè)對(duì)象要消耗的資源過多,或者要訪問IO和數(shù)據(jù)庫等資源.
配置文件,工具類,線程池,緩存,日志對(duì)象等。
UML類圖

角色介紹:
. Client——高層客戶端
. Singleton——單例類
實(shí)現(xiàn)單例模式的關(guān)鍵點(diǎn):
- 構(gòu)造函數(shù)不對(duì)外開放,一般為Private。就是不允許外部通過new Singleton()來獲取對(duì)象。
- 通過一個(gè)靜態(tài)方法或枚舉返回單例類對(duì)象,如getInstance()方法。
- 確保單例類的對(duì)象只有一個(gè),尤其是在多線程的情況下。確保即使在多線程也能實(shí)現(xiàn)單例。
- 確保單例類對(duì)象在反序列化時(shí)不會(huì)重新構(gòu)建對(duì)象。
實(shí)現(xiàn)方式
餓漢式
public class Singleton {
private static final Singleton mSingleton = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return mSingleton;
}
}
里面的對(duì)象是個(gè)靜態(tài)對(duì)象,第一次聲明的時(shí)候就會(huì)實(shí)現(xiàn)初始化。外部只能通過getInstance()獲取到這個(gè)對(duì)象,實(shí)現(xiàn)單例。
懶漢式
public class Singleton {
private static Singleton mSingleton ;
private Singleton(){}
public static synchronized Singleton getInstance(){
if (mSingleton==null){
mSingleton=new Singleton();
}
return mSingleton;
}
}
這里的getInstance()方法加上了synchronized關(guān)鍵字,保證了多線程也能實(shí)現(xiàn)單例。
優(yōu)點(diǎn):
單例只有在使用時(shí)才會(huì)被實(shí)例化,一定程度上節(jié)約了資源。
缺點(diǎn):
(1)單例在第一次加載時(shí)要及時(shí)進(jìn)行實(shí)例化,反應(yīng)稍慢
(2)每次調(diào)用getInstance()都要進(jìn)行同步,造成不必要的同步開銷。
所以一般不建議用。
Double Check Lock(DCL)雙重檢查鎖
public class Singleton {
private static Singleton mSingleton ;
private Singleton(){}
public static Singleton getInstance(){
if (mSingleton==null){
synchronized (Singleton.class){
if (mSingleton==null){
mSingleton=new Singleton();
}
}
}
return mSingleton;
}
}
這個(gè)getInstance方法中對(duì)mSingleton進(jìn)行了兩次判空:第一次是為了避免不必要的同步鎖;第二層是為了在null的時(shí)候創(chuàng)建實(shí)例。
DCL失效:
在多線程下,假設(shè)A線程執(zhí)行到mSingleton=new Singleton()的時(shí)候,CPU并不是一次性執(zhí)行完這條語句的,因?yàn)檫@不是一個(gè)原子操作(指不會(huì)被線程調(diào)度機(jī)制打斷的操作)。
舉個(gè)例子:執(zhí)行 Timer timer = new Timer(); 通過字節(jié)碼文件可以看到這一行代碼編譯出來是這樣的:
0: new #2 // class java/util/Timer
3: dup
4: invokespecial #3 // Method java/util/Timer."<init>":()V
7: astore_1
8: return
所以mSingleton=new Singleton()大致做了三件事:
(1)給Singleton的實(shí)例分配內(nèi)存
(2)調(diào)用Singleton的構(gòu)造方法
(3)將mSingleton指向分配的內(nèi)存空間(這個(gè)時(shí)候mSingleton才不為空)
由于Java編譯器允許處理器亂序執(zhí)行,所以上面的第二步第三步的執(zhí)行順序沒法得到保證。執(zhí)行順序可能是1-2-3也可能是1-3-2。
當(dāng)A線程執(zhí)行順序是1-3-2的時(shí)候,如果執(zhí)行到了1-3,第2步還沒執(zhí)行的時(shí)候,如果B線程判斷mSingleton==null的時(shí)候就會(huì)的發(fā)哦FALSE的結(jié)果,從而返回一個(gè)錯(cuò)誤的單例。
優(yōu)點(diǎn):
資源利用率高,第一次執(zhí)行g(shù)etInstance的時(shí)候才會(huì)被實(shí)例化,效率高。
缺點(diǎn):
第一冊(cè)加載反應(yīng)稍慢,而且有失敗的可能,但是概率很小。
這種是用的最多的單例實(shí)現(xiàn)方式,大部分情況下都能保證單例。
靜態(tài)內(nèi)部類
public class Singleton {
private Singleton(){}
public static Singleton getInstance(){
return SingletonHolder.mSingleton;
}
private static class SingletonHolder{
private static final Singleton mSingleton = new Singleton();
}
}
當(dāng)?shù)谝淮渭虞dSingleton的時(shí)候并不會(huì)初始化mSingleton,只有在第一次調(diào)用getInstance的時(shí)候才會(huì)加載SIngletonHolder類。
優(yōu)點(diǎn):
不僅能保證線程安全,也能保證單例的唯一性,也延遲了單例的實(shí)例化,比較推薦。
枚舉單例
public enum Singleton{
INSTANCE;
public void doThing(){
System.out.println(this.hashCode());
}
}
使用時(shí)可以通過Singleton singleton = Singleton.INSTANCE;
來獲取單例。
優(yōu)點(diǎn):
寫法簡單,而且默認(rèn)線程安全,任何情況下都是一個(gè)單例。
特點(diǎn):
上面的幾種在有一種情況下會(huì)單例失效,出現(xiàn)重復(fù)創(chuàng)建對(duì)象,那就是反序列化。
反序列化的時(shí)候會(huì)調(diào)用一個(gè)readResolve()方法重新生成一個(gè)實(shí)例,所以上面的幾種方式要解決這個(gè)問題需要加入以下方法,:
public class Singleton {
private Singleton(){}
public static Singleton getInstance(){
return SingletonHolder.mSingleton;
}
private static class SingletonHolder{
private static final Singleton mSingleton = new Singleton();
}
private Object readRedolve() throws ObjectStreamException{
return SingletonHolder.mSingleton;
}
}
使用容器實(shí)現(xiàn)單例
public class SingletonManager {
private static Map<String,Objects> objMap = new HashMap<>();
private SingletonManager(){}
public static void registerService(String key,Object obj){
if (!objMap.containsKey(key)){
objMap.put(key,obj);
}
}
public static Object getService(String key){
return objMap.get(key);
}
}
在程序的開始,將許多要單例的對(duì)象放到一個(gè)容器里,用的時(shí)候根據(jù)key取得對(duì)應(yīng)的單例對(duì)象。
Android源碼中的單例模式
源碼中的單例模式太多了,甚至有一個(gè)專門的單例的抽象類:
package android.util;
public abstract class Singleton<T> {
private T mInstance;
protected abstract T create();
public final T get() {
synchronized (this) {
if (mInstance == null) {
mInstance = create();
}
return mInstance;
}
}
}
我們經(jīng)常通過context.getSystemService(String name)來獲取一些系統(tǒng)服務(wù),如在Activity中獲取ActivityManager:
ActivityManager mActivityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
書中舉例為LayoutInflater,平時(shí)獲取方式為LayoutInflater.from(context)
,看下這個(gè)方法:
package android.view;
public static LayoutInflater from(Context context) {
LayoutInflater LayoutInflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (LayoutInflater == null) {
throw new AssertionError("LayoutInflater not found.");
}
return LayoutInflater;
}
發(fā)現(xiàn)也是通過調(diào)用context.getSystemService(String name)獲取的。
那么扎個(gè)單例是怎么實(shí)現(xiàn)的呢?順著代碼往上看吧。。
context.getSystemService(String name)直接點(diǎn)進(jìn)去的話會(huì)進(jìn)到
package android.content;
public abstract class Context {
...
public abstract Object getSystemService(@ServiceName @NonNull String name);
...
}
通過分析activity的啟動(dòng)流程可以知道,Context的功能的具體實(shí)現(xiàn)是在ContextImpl.java中,看具體實(shí)現(xiàn)代碼:
package android.app;
class ContextImpl extends Context {
...
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
}
...
然后繼續(xù)SystemServiceRegistry.getSystemService(this, name):
package android.app;
final class SystemServiceRegistry {
...
//用來getSystemService的容器,里面存放的是ServiceFetcher<?>
private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
new HashMap<String, ServiceFetcher<?>>();
...
//靜態(tài)代碼塊,第一次加載時(shí)執(zhí)行,而且只會(huì)執(zhí)行一次,保證了注冊(cè)的服務(wù)的唯一性。
static {
registerService(Context.ACCESSIBILITY_SERVICE, AccessibilityManager.class,
new CachedServiceFetcher<AccessibilityManager>() {
@Override
public AccessibilityManager createService(ContextImpl ctx) {
return AccessibilityManager.getInstance(ctx);
}});
registerService(Context.DOWNLOAD_SERVICE, DownloadManager.class,
new CachedServiceFetcher<DownloadManager>() {
@Override
public DownloadManager createService(ContextImpl ctx) {
return new DownloadManager(ctx);
}});
...
//還有很多服務(wù)注冊(cè)
}
...
//靜態(tài)代碼塊中調(diào)用這個(gè)方法,把服務(wù)名和創(chuàng)建的服務(wù)對(duì)應(yīng)放在容器中,實(shí)現(xiàn)單例。
private static <T> void registerService(String serviceName, Class<T> serviceClass,
ServiceFetcher<T> serviceFetcher) {
SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
}
...
public static Object getSystemService(ContextImpl ctx, String name) {
ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
}
...
}
里面還不是直接拿到服務(wù),而是調(diào)用了fetcher.getService(ctx)來獲取服務(wù)。看看ServiceFetcher<?>:
static abstract interface ServiceFetcher<T> {
T getService(ContextImpl ctx);
}
這是個(gè)接口,看上面的靜態(tài)代碼塊里面的方法發(fā)現(xiàn)注冊(cè)服務(wù)的時(shí)候都是用的CachedServiceFetcher這個(gè)類:
static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> {
private final int mCacheIndex;
public CachedServiceFetcher() {
mCacheIndex = sServiceCacheSize++;
}
@Override
@SuppressWarnings("unchecked")
public final T getService(ContextImpl ctx) {
//ctx.mServiceCache是獲取一個(gè)數(shù)組:new Object[sServiceCacheSize];
//數(shù)組的長度就是構(gòu)造方法中的那個(gè)變量,每注冊(cè)一個(gè)服務(wù),就會(huì)new一個(gè)對(duì)應(yīng)的CachedServiceFetcher,然后數(shù)組長度就+1。第一次獲取到這個(gè)數(shù)組肯定是個(gè)空數(shù)組
final Object[] cache = ctx.mServiceCache;
synchronized (cache) {
// Fetch or create the service.
Object service = cache[mCacheIndex];
//第一次獲取這個(gè)服務(wù)的時(shí)候,數(shù)組是空的 ,所以service == null為TRUE。
if (service == null) {
//調(diào)用注冊(cè)時(shí)實(shí)現(xiàn)的createService方法,把生成的具體服務(wù)放在數(shù)組對(duì)應(yīng)下標(biāo)中,
//之后就直接從數(shù)組中獲取了。實(shí)現(xiàn)了單例。
service = createService(ctx);
cache[mCacheIndex] = service;
}
return (T)service;
}
}
// 在靜態(tài)代碼塊中實(shí)現(xiàn)
public abstract T createService(ContextImpl ctx);
}
里面有個(gè)抽象方法,需要實(shí)例化的時(shí)候?qū)崿F(xiàn)。在靜態(tài)代碼塊中的方法都實(shí)現(xiàn)了這個(gè)createService(ContextImpl ctx)方法,并且返回了對(duì)應(yīng)的服務(wù)。
附上部分注冊(cè)服務(wù)的截圖,截不下:

總結(jié)
優(yōu)點(diǎn):
- 由于單例模式在內(nèi)存中只有一個(gè)實(shí)例,減少了內(nèi)存開支,特別是一個(gè)對(duì)象需要頻繁創(chuàng)建,或者創(chuàng)建或銷毀時(shí)性能無法優(yōu)化,單例模式的優(yōu)勢(shì)就很明顯了。
- 由于只生成一個(gè)實(shí)例,減少了系統(tǒng)性能開銷。
- 可以避免對(duì)資源的多重占用,如文件操作等。
- 單例模式可以設(shè)置為全局的訪問點(diǎn),優(yōu)化和共享資源訪問。
缺點(diǎn)
- 單例模式一般沒有接口,拓展很困難,基本都要修改源代碼。
- 在Android中,如果單例模式持有Activity的Context,容易產(chǎn)生內(nèi)存泄漏。所以盡量用ApplicationContext。