Android 单例模式的正确姿势

作者: AntDream | 来源:发表于2018-07-19 09:13 被阅读64次

    单例模式是使用得最多的设计模式,模版代码也很多。但是如果使用不当还是容易出问题。

    DCL模式(双重检查锁定模式)的正确使用方式

    一般我们使用DCL方法来实现单例模式时都是这样的模版代码:

    private static Singleton mSingleton = null;
    private Singleton () {}
    
    public static Singleton getInstance() {
        if (mSingleton == null) {
            synchronized (Singleton.class) {
                if (mSingleton == null) {
                    mSingleton = new Singleton();
                }
            }
        }
        return mSingleton;
    
    }
    

    实际上,上述方法在多线程的环境下,还是会有可能创建多个实例。为什么呢?

    mConstant = new Constant()这行代码虚拟机在执行的时候会有多个操作,大致包括:

    • 为新的对象分配内存
    • 调用Constant的构造方法,初始化成员变量
    • 将mConstant这个引用指向新创建的Constant对象的地址

    在多线程环境下,每个线程的私有内存空间中都有mSingleton的副本。这导致可能存在下面的情况:

    • 当在一个线程中初始化mSingleton后,主内存中的mSingleton变量的值可能并没有及时更新;
    • 主内存的mSingleton变量已经更新了,但在另一个线程中的mSingleton变量没有即时从主内存中读取最新的值

    这样的话就有可能创建多个实例,虽然这种几率比较小。

    那怎么解决这个问题呢?答案是使用volatile关键字

    volatile关键字能够保证可见性,被volatile修饰的变量,在一个线程中被改变时会立刻同步到主内存中,而另一个线程在操作这个变量时都会先从主内存更新这个变量的值。

    更保险的单例模式实现

    private volatile static Singleton mSingleton = null;
    private Singleton () {}
    
    public static Singleton getInstance() {
        if (mSingleton == null) {
            synchronized (Singleton.class) {
                if (mSingleton == null) {
                    mSingleton = new Singleton();
                }
            }
        }
        return mSingleton;
    
    }
    

    使用单例模式,小心内存泄漏了喔~

    单例模式的静态特性导致它的对象的生命周期是和应用一样的,如果不注意这一点就可能导致内存泄漏。下面看看常见的2种情况

    • Context的泄漏
    //SingleInstance.class
    private volatile static SingleInstance mSingleInstance = null;
    private SingleInstance (Context context) {}
    
    public static SingleInstance getInstance(Context context) {
        if (mSingleInstance == null) {
            synchronized (SingleInstance.class) {
                if (mSingleInstance == null) {
                    mSingleInstance = new SingleInstance(context);
                }
            }
        }
        return mSingleInstance;
    
    }
    
    //MyActivity
    public class MyActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            
            //这样就容易出问题了
            SingleInstance singleInstance = SingleInstance.getInstance(this);
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
        }
    }
    

    如上面那样直接传入MyActivity的引用,如果当前MyActivity退出了,但应用还没有退出,singleInstance一直持有MyActivity的引用,MyActivity就不能被回收了。

    解决方法也很简单,传入ApplicationContext就可以了。

    SingleInstance singleInstance = SingleInstance.getInstance(getApplicationContext());
    
    • View的泄漏

    如果单例模式的类中有跟View相关的属性,就需要注意了。搞不好也会导致内存泄漏,原因和上面分析的原因一样。

    //SingleInstance.class
    private volatile static SingleInstance mSingleInstance = null;
    private SingleInstance (Context context) {}
    
    public static SingleInstance getInstance(Context context) {
        if (mSingleInstance == null) {
            synchronized (SingleInstance.class) {
                if (mSingleInstance == null) {
                    mSingleInstance = new SingleInstance(context);
                }
            }
        }
        return mSingleInstance;
    
    }
    
    //单例模式中这样持有View的引用会导致内存泄漏
    private View myView = null;
    public void setMyView(View myView) {
        this.myView = myView;
    }
    

    解决方案是采用弱引用

    private volatile static SingleInstance mSingleInstance = null;
    private SingleInstance (Context context) {}
    
    public static SingleInstance getInstance(Context context) {
        if (mSingleInstance == null) {
            synchronized (SingleInstance.class) {
                if (mSingleInstance == null) {
                    mSingleInstance = new SingleInstance(context);
                }
            }
        }
        return mSingleInstance;
    
    }
    
    //    private View myView = null;
    //    public void setMyView(View myView) {
    //        this.myView = myView;
    //    }
    
    //用弱引用
    private WeakReference<View> myView = null;
    public void setMyView(View myView) {
        this.myView = new WeakReference<View>(myView);
    }
    

    很多东西虽然简单,还是有我们需要注意的地方。这就需要我们理解它们的特性了。比如上面用了弱引用来解决内存泄漏的问题,那我们就需要明白弱引用的特点,需要注意使用弱引用的变量可能为空的问题

    被弱引用关联的对象只能生存到下一次垃圾收集发生之前,当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象


    今天你进步了嘛?欢迎关注我的微信公众号,和我一起每天进步一点点!


    AntDream

    相关文章

      网友评论

        本文标题:Android 单例模式的正确姿势

        本文链接:https://www.haomeiwen.com/subject/qrhapftx.html