在上一篇中,讨论了单例的4种基本形态,这次我们来探讨单例的变形。
1.有限个数的单例形式。即这个对象可能有多个,从这个角度上说,它其实不属于单例,但实现方式确是以单例为基础的。它通常是以带参数的getInstance(或其变型)存在。
public class MultiInstanceDemo { private String mType; private MultiInstanceDemo(String type) { mType = type; } //单例 //每种类型限制只能有一个实例 private static final HashMap<String, MultiInstanceDemo> mInstanceMap = new HashMap<String, MultiInstanceDemo>(); public static MultiInstanceDemo getInstance(String type) { MultiInstanceDemo instance = mInstanceMap.get(type); if( instance == null) { synchronized (MultiInstanceDemo.class) { instance = mInstanceMap.get(type); if( instance == null) { //double check instance = new MultiInstanceDemo(type); mInstanceMap.put(type,instance); } } } return instance; } }
从上面的代码中可以看到,getInstance是带了一个参数,这个参数其实是做为Key来保证对于相同的Key,只创建同一个对象。从另一个角度来说,这个getInstance实际上是被做工厂方法来使用。
在Android中,最常用到的一个Context.getSystemService(String), 其实就是用了这种结构。
在ContextImpl.java中,我们看到有这么一个结构。
private static final HashMap<String, ServiceFetcher> SYSTEM_SERVICE_MAP = new HashMap<String, ServiceFetcher>();
private static int sNextPerContextServiceCacheIndex = 0; private static void registerService(String serviceName, ServiceFetcher fetcher) { if (!(fetcher instanceof StaticServiceFetcher)) { fetcher.mContextCacheIndex = sNextPerContextServiceCacheIndex++; } SYSTEM_SERVICE_MAP.put(serviceName, fetcher); }
registerService(ACCESSIBILITY_SERVICE, new ServiceFetcher() { public Object getService(ContextImpl ctx) { return AccessibilityManager.getInstance(ctx); }}); registerService(CAPTIONING_SERVICE, new ServiceFetcher() { public Object getService(ContextImpl ctx) { return new CaptioningManager(ctx); }}); registerService(ACCOUNT_SERVICE, new ServiceFetcher() { public Object createService(ContextImpl ctx) { IBinder b = ServiceManager.getService(ACCOUNT_SERVICE); IAccountManager service = IAccountManager.Stub.asInterface(b); return new AccountManager(ctx, service); }}); registerService(ACTIVITY_SERVICE, new ServiceFetcher() { public Object createService(ContextImpl ctx) { return new ActivityManager(ctx.getOuterContext(), ctx.mMainThread.getHandler()); }});
@Override public Object getSystemService(String name) { ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name); return fetcher == null ? null : fetcher.getService(this); }
2. 单例的生命周期讨论
一般说来,单例的对象是存在静态变量中的,除非是进程被杀死,这个单例就会永远存在。那么,有没有必要写一个Destory方法,把这个instance设为null, 以节约内存呢?
我的意见是这种做会破坏单例的唯一性。
因为你不知道在程序的哪个角落保留了对当前这个单例的引用。一旦在单例对象(mInstance=null)设为null后,下次当别人调用getInstance时,会又重新生成单例,此时内存中其实是同时存在两个及以上的对象,这就破坏了单例的唯一性。
因此,不建议去释放单例所占据的内存,故请谨慎使用单例。
那问题来了,如果我想提高内存的使用效率,只想创建一个短命的单例对象怎么办?
通常情况下,那就不能以常规的方法来创建或是在静态变量中存放单例的实例。需要把这个“单例”的对象作为另一个带有生命周期的对象的成员。这里写了一个例子来探讨这种情况。
SingleMethod.java,这是一个接口,表明单例中用到的所有公开的方法
/** * Created by Rex on 4/12/2015. */ //很折腾的一个接口,包含单例中所有public的方法 public interface SingleMethod { public void methodA(); public void methodB(); }
ShortSingle.java,这个是真正的单例,但对外不可见,无法直接访问
/** * Created by Rex on 4/12/2015. */ //真正的单例在这儿了 class ShortSingle implements SingleMethod { /* package */ ShortSingle() { } @Override public void methodA() { } @Override public void methodB() { } }
singleVistor.java,用来访问单例的方法,提供给外部使用,这个对象可创建多次,可保留多个引用,但最关键的是传入的single必须是真正的单例。
/** * Created by Rex on 4/12/2015. */ //单例的访问者,此对象允许存在多个 public class SingleVistor implements SingleMethod { private ShortSingle mSingle; /* package */ SingleVistor(ShortSingle single) { mSingle = single; } @Override public void methodA() { final ShortSingle shortSingle = mSingle; if( shortSingle != null) { shortSingle.methodA(); } } @Override public void methodB() { final ShortSingle shortSingle = mSingle; if( shortSingle != null) { shortSingle.methodB(); } } /* package */ void destory() { mSingle = null; } }
LifeCycleObject.java 这是一个具有生命周期的类,init是开始, destory是结束。
/** * Created by Rex on 4/11/2015. */ public class LifeCycleObject { //真正的单例,我们要确保这个对象只能有一份 private static ShortSingle mSingle; //单例对象的访问者,外面通过这个对象来使用单例 private static SingleVistor mSingleVistor; //既然是短命的对象,自然就有开始与结束 public static void init() { synchronized (LifeCycleObject.class) { if( mSingle == null) { mSingle = new ShortSingle(); } if( mSingleVistor == null) { mSingleVistor = new SingleVistor(mSingle); } } } //销毁了对象 public static void destory() { synchronized (LifeCycleObject.class) { mSingle = null; if( mSingleVistor != null) { mSingleVistor.destory(); mSingleVistor = null; } } } //返回一个包含单例方法一个接口,外面使用就不管它到底是什么对象,由于有了生命周期,则是有可能为null的 public static SingleMethod getSingleObject() { return mSingleVistor; } //for test only static SingleMethod getRealSingle() { return mSingle; } }
在LifeCycleObject的生命周期中,从第一次调用init到destory之间,中间不管init调用多次,或是创建了多少个LifeCycleObject的对象,其中ShortSingle这个真正的单例只生一个。而且,这个单例本身对外面是隐藏的,外面无法获取这个单例的引用,外面访问的是单例的一个接口,即SingleMethod, 的另一个实现SingleVistor, 同样,这个SingleVistor的引用保持并不破坏单例的性质。因为就算保留了引用,但在destroy中,这个引用会置空。这样就达到了严格控制单例对象的目的。
在destroy之后,则单例在内存中的引用设置为null,所占用内存就释放了。到一下次的重新init, 此时单例会创建新的对象,但始终保持内存中最多只有一份单例对象。
这种设计优势是能较好的控制单例的生命周期,但使用成本较高,维护不方便,每次修改单例,需要修改三个类,而且结构复杂,读起代码比较痛苦。因此,如果不是对内存的要求特别苛刻,不推荐使用。
3.另一种带参数的单例,是需要初始化的。这种单例用起来也需要小心。最常见的场景是在Android中,有时候代码如网络,数据库等模块,需要一个applicationcontext作为参数。这种怎么处理呢,建议的写法是把参数单独提出来,做一个init或是setup的方法,然后在必要时才创建单例,这样对使用者友好,而且内存使用效率也不错。
public class SingletonWithParam { private volatile static SingletonWithParam mInstance; private static Object mParam; private Object mBigObject; //lazy init private SingletonWithParam(Object param) { //create other object with param mBigObject = new Object(); } //initialize when the app start public static void setup(Object param) { mParam = param; } public static SingletonWithParam getInstance() { if( mInstance == null) { synchronized (SingletonWithParam.class) { if( mInstance == null) { //double check mInstance = new SingletonWithParam(mParam); } } } return mInstance; } }
4.单例的破坏
在通常情况下,我们写的单例是能正常工作的。但这世界总有一些例外,请看下面的代码。
这是一个我们常写的单例模式
public class SimpleInstance { private volatile static SimpleInstance mInstance; private SimpleInstance() { } public static SimpleInstance getInstance() { if( mInstance == null) { synchronized (SingletonWithParam.class) { if( mInstance == null) { //double check mInstance = new SimpleInstance(); } } } return mInstance; } }
这是一个单元测试
public class SimpleInstanceTest { @Test public void instanceTest() { SimpleInstance simpleInstance1 = SimpleInstance.getInstance(); SimpleInstance simpleInstance2 = null; try { Constructor<SimpleInstance> constructor = SimpleInstance.class.getDeclaredConstructor(); constructor.setAccessible(true); simpleInstance2 = constructor.newInstance(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } assertNotNull(simpleInstance2); assertNotEquals(simpleInstance1,simpleInstance2); } }
而测试的结果是simpleInstance1并不等于simpleInstance2, 也就是说,对于通过反射的方式,是可以破坏单例的性质的。因此,通常情况下,我们的代码是防君子不防小人。那有办法防止吗? 额,有一种招术叫防御性编码,我们可以使用一个小技巧。
在构造函数加上一个assert语句。
private SimpleInstance() { assert(mInstance == null); }
这样,想反射我的构造函数?没门。
除了反射,还有其他方式破坏吗?有,单例的序列化,网上有关这个的讨论很多,这里也不浪费篇幅了。直接上结论吧,为了防止单例的性质不被破坏,需要加上这么一个方法:
private Object readResolve() { return mInstance; }
最后总结下,在本文中,我们讨论了关于单例的一些扩展性应用,包含生命周期,带参数的构造函数,有限个数的单例,及单例的破坏等话题,欢迎大家来拍砖。
原文地址:http://blog.csdn.net/zoudifei/article/details/44995511