单例模式的几种写法
在开发中,我们总是会遇到使用单例模式的情况,今天就来总结一下几种实现单例模式的方法。
1.单例模式(懒汉式)的最佳写法,适用于单线程。
public class Singleton {
private static Singleton singleton;
private Singleton() {
}
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
这种写法只能在单线程下使用。如果是多线程,可能发生一个线程通过并进入了 if (singleton == null) 判断语句块,但还未来得及创建新的实例时,另一个线程也通过了这个判断语句,两个线程最终都进行了创建,导致多个实例的产生。所以在多线程环境下必须摒弃此方式。
这种写法线程不安全。
2.加入同步锁。
public class Singleton {
private static Singleton singleton;
private Singleton() {
}
public static synchronized Singleton getInstance(){
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
通过为 getInstence() 方法增加 synchronized 关键字,迫使每个线程在进入这个方法前,要先等候别的线程离开该方法,即不会有两个线程可以同时进入此方法执行 new Singleton(),从而保证了单例的有效。但它的致命缺陷是效率太低了,每个线程每次执行 getInstance() 方法获取类的实例时,都会进行同步。而事实上实例创建完成后,同步就变为不必要的开销了,这样做在高并发下必然会拖垮性能。所以此方法虽然可行但也不推荐。
网上这样的代码更多,可以很好的工作,但是缺点是效率低。
3.实际上有了一种更好的双重校验锁写法。
public class Singleton {
private volatile static Singleton singleton;
private Singleton() {
}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
DCL体现在进行了两次 if (singleton == null) 的检查,这样既同步代码块保证了线程安全,同时实例化的代码也只会执行一次,实例化后同步操作不会再被执行,从而效率提升很多。
双重检查锁定(DCL)方式也是延迟加载的,它唯一的问题是,由于 Java 编译器允许处理器乱序执行,在 JDK 版本小于 1.5 时会有 DCL 失效的问题(原因解释详见附录 2)。当然,现在大家使用的 JDK 普遍都已超过 1.4,只要在定义单例时加上 1.5 及以上版本具体化了的 volatile 关键字,即可保证执行的顺序,从而使单例起效。所以 DCL 方式是推荐的一种方式。
Android 中鼎鼎大名的 Universal Image Loader 和 EventBus 都是采用了这种方式的单例
这才是最佳写法!!!
在Android中使用单例模式可能会内存泄露。
public class CommUtils {
private volatile static CommUtils mCommUtils;
private Context mContext;
public CommUtils(Context context) {
mContext=context;
}
public static CommUtils getInstance(Context context) {
if (mCommUtils == null) {
synchronized (CommUtils.class) {
if (mCommUtils == null) {
mCommUtils = new CommUtils(context);
}
}
}
return mCommUtils;
}
}
凡是跟UI相关的,都应该使用Activity做为Context来处理;其他的一些操作,Service,Activity,Application等实例都可以,当然了,注意Context引用的持有,防止内存泄漏。
如何获取Context
通常我们想要获取Context对象,主要有以下四种方法:
1:View.getContext,返回当前View对象的Context对象,通常是当前正在展示的Activity对象。
2:Activity.getApplicationContext,获取当前Activity所在的(应用)进程的Context对象,通常我们使用Context对象时,要优先考虑这个全局的进程Context。
3:ContextWrapper.getBaseContext():用来获取一个ContextWrapper进行装饰之前的Context,可以使用这个方法,这个方法在实际开发中使用并不多,也不建议使用。
4:Activity.this 返回当前的Activity实例,如果是UI控件需要使用Activity作为Context对象,但是默认的Toast实际上使用ApplicationContext也可以。
解决办法
能使用Application的Context就不要使用Activity的Context,Application的生命周期伴随着整个进程的周期
正确使用Context
一般Context造成的内存泄漏,几乎都是当Context销毁的时候,却因为被引用导致销毁失败,而Application的Context对象可以理解为随着进程存在的:
1:当Application的Context能搞定的情况下,并且生命周期长的对象,优先使用Application的Context。
2:不要让生命周期长于Activity的对象持有到Activity的引用。
3:尽量不要在Activity中使用非静态内部类,因为非静态内部类会隐式持有外部类实例的引用,如果使用静态内部类,将外部实例引用作为弱引用持有。
错误的单例模式
public class Singleton {
private static Singleton instance;
private Context mContext;
private Singleton(Context context) {
this.mContext = context;
}
public static Singleton getInstance(Context context) {
if (instance == null) {
instance = new Singleton(context);
}
return instance;
}
}
这是一个非线程安全的单例模式,instance作为静态对象,其生命周期要长于普通的对象,其中也包含Activity,假如Activity A去getInstance获得instance对象,传入this,常驻内存的Singleton保存了你传入的Activity A对象,并一直持有,即使Activity被销毁掉,但因为它的引用还存在于一个Singleton中,就不可能被GC掉,这样就导致了内存泄漏。