Android 单例模式的正确姿势

简介:

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

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

目录
相关文章
|
设计模式 SQL 缓存
Android 单例模式必知必会
一、概念 单例模式是运用最广泛的设计模式之一,在应用这个模式时,单例模式的类必须保证只有一个实例存在。多用于整个程序只需要有一个实例,通常很消耗资源的类,比如线程池,缓存,网络请求,IO操作,访问数据库等。由于类比较耗资源,所以没必要让它构造多个实例,这种就是单例模式比较好的使用场景。
180 0
|
安全 Java Android开发
我要做 Android 之单例模式
Q:实现单例模式有几种方法?懒汉式中双层锁的目的是什么?两次判空的目的又是什么? 懒汉式(线程不安全) 单例模式最后的目的无非就是获取当前存在的实例对象,如果没有实例对象就实例化一个,有就直接返回。
1196 0
|
安全 Java 缓存
Android--单例模式
版权声明:本文为博主原创文章,转载请标明出处。 https://blog.csdn.net/chaoyu168/article/details/78897883 ###定义 保证一个类仅有一个实例,并提供一个访问它的全局访问点。
829 0
|
API Android开发
Android设计模式系列-单例模式
版权声明:本文为博主原创文章,转载请标明出处。 https://blog.csdn.net/chaoyu168/article/details/52592563 单例模式,可以说是GOF的23种设计模式中最简单的一个。
924 0
|
API 开发工具 Android开发
Android设计模式系列(3)--SDK源码之单例模式
原文链接:http://www.cnblogs.com/qianxudetianxia/archive/2011/08/07/2130306.html 单例模式,可以说是GOF的23种设计模式中最简单的一个。
987 0
|
3天前
|
Android开发 开发者 Kotlin
探索安卓开发中的新特性
【9月更文挑战第14天】本文将引导你深入理解安卓开发领域的一些最新特性,并为你提供实用的代码示例。无论你是初学者还是经验丰富的开发者,这篇文章都会给你带来新的启示和灵感。让我们一起探索吧!
|
7天前
|
IDE 开发工具 Android开发
安卓与iOS开发对比:平台选择对项目成功的影响
【9月更文挑战第10天】在移动应用开发的世界中,选择正确的平台是至关重要的。本文将深入探讨安卓和iOS这两大主要移动操作系统的开发环境,通过比较它们的市场份额、开发工具、编程语言和用户群体等方面,为开发者提供一个清晰的指南。我们将分析这两个平台的优势和劣势,并讨论如何根据项目需求和目标受众来做出最佳选择。无论你是初学者还是有经验的开发者,这篇文章都将帮助你更好地理解每个平台的特性,并指导你做出明智的决策。
|
3天前
|
XML 编解码 Android开发
安卓开发中的自定义视图控件
【9月更文挑战第14天】在安卓开发中,自定义视图控件是一种高级技巧,它可以让开发者根据项目需求创建出独特的用户界面元素。本文将通过一个简单示例,引导你了解如何在安卓项目中实现自定义视图控件,包括创建自定义控件类、处理绘制逻辑以及响应用户交互。无论你是初学者还是有经验的开发者,这篇文章都会为你提供有价值的见解和技巧。
10 3
|
5天前
|
API Android开发 iOS开发
安卓与iOS开发中的线程管理对比
【9月更文挑战第12天】在移动应用的世界中,安卓和iOS平台各自拥有庞大的用户群体。开发者们在这两个平台上构建应用时,线程管理是他们必须面对的关键挑战之一。本文将深入探讨两大平台在线程管理方面的异同,通过直观的代码示例,揭示它们各自的设计理念和实现方式,帮助读者更好地理解如何在安卓与iOS开发中高效地处理多线程任务。
|
7天前
|
开发框架 Android开发 iOS开发
探索安卓与iOS开发的差异:构建未来应用的指南
在移动应用开发的广阔天地中,安卓与iOS两大平台各占半壁江山。本文将深入浅出地对比这两大操作系统的开发环境、工具和用户体验设计,揭示它们在编程语言、开发工具以及市场定位上的根本差异。我们将从开发者的视角出发,逐步剖析如何根据项目需求和目标受众选择适合的平台,同时探讨跨平台开发框架的利与弊,为那些立志于打造下一个热门应用的开发者提供一份实用的指南。
19 5