什么是单例模式
单例模式是应用最广的设计模式之一。
在很多时候,一个应用程序对于一个类只需要一个全局的实例对象以供调用,创建多个对象会导致对系统资源的浪费且没有任何意义,单例模式就为此而生。
单例模式的实现思路
应确保这个类只能有一个实例,且自行实例化向整个系统提供这个实例。
为了能够实现上述思路,我们可有想出下面的几个点。
- 为了确保调用者无法自行创建该类的实例化对象,所以应该将该类的构造方法用private修饰。
- 因为该对象无法通过new创建,所以类中应有一个该类的静态实例化对象作为调用的返回值。
- 为了将上述的静态实例化对象返回,应有一个公开的方法将该对象作为返回值返回。
单例的各种实现方式
有了思路,我们来一起看一下实现方法。
懒汉式单例
所谓懒汉式,顾名思义它比较“懒”,它在类加载的时候并不会去实例化应返回的单例对象,而是当调用时才去判空来判断是否创建该对象然后才作为返回值返回。
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
饿汉式单例
而饿汉式却与上面的懒汉是不同,它不管你是否调用,我在类加载的时候就去创建这个对象,方法调用时直接返回。
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
以上提到的两种并没有考虑多线程的情况,再高并发的情况下,仍有可能会出现创建多个对象的可能,懒汉式虽然在方法前加上synchronized
可以解决,但由于每次调用都需要去进行同步,造成不必要的开销,所以这种方式不推荐使用。
DCL单例
全称是Doucle Check Lock,这种实现方式既能在需要时才初始化单例,又能够保证线程安全,但是由于java内存模型的原因或在高并发情况下仍有可能会失败,但是由于Android开发过程中一般不会出现高并发的情况,所以仍是使用最多的一种单例模式。
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
解释一下这里为什么会用到双重判空,简单来说就是由于JVM的原因,如果你仅仅是单重判空,可能会发生这样的情况:假设有两个线程,在第一个线程判空成功准备去创建该对象的这个时候,可能另一个线程刚好完成了创建,这个时候就出现了问题,所以在这里我们采用这种写法,能有效的避免这种情况的发生。
静态内部类单例
public class Singleton {
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
}
由于静态内部类是线程安全的,所以这种方式肯定是没有任何问题的。
枚举单例
public enum Singleton {
INSTANCE;
}
和上面的方式一样,只不过用枚举来实现,因为枚举也默认是线程安全的。只不过由于Android开发中并不推荐使用枚举类,所以很少看到。
单例模式的优缺点
优点
- 减少了内存开支(只能创建一个对象)。
- 减少了性能开销(通过永驻内存来解决对象的频繁创建与销毁)。
- 避免对资源的重复占用(只有一个对象去对另一个对象进行操作)。
- 优化了资源访问(可以全局访问)。
缺点
- 扩展性差(单例模式一般没有接口,所以扩展性差,如果要扩展,一般只能通过直接修改代码的方式来扩展)。
- 如果单例对象持有Context,容易引发内存泄露,所以一般传递给单例对象的Context都是Application Context。