Android设计模式之单例模式

本文详细介绍了单例模式的概念、作用、优势和适用场景,强调了其在Android开发中的重要性。文中列举了多种单例模式的实现方式,包括饿汉式、懒汉式、双重检查锁定(DCL)等,并分析了各自的优缺点和线程安全性。最后,提到了内部类、枚举和静态内部类等实现单例的高效方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简介

单例模式是设计模式中最常见也最为简单的一种设计模式,确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例的访问方法。通过单例模式可以保证系统中一个类只有一个实例,我们经常使用的图片加载框架Universal-Image-Loader的实例创建就是使用了单例模式,因为Universal-Image-Loader中包含有线程池、缓存系统、网络请求等配置很消耗资源,不应该创建多个对象,因此就需要用到单例模式来实现。

作用

单例模式(Singleton):确保某一个类仅有一个实例,并提供一个访问它的全局访问点。

优势

  1. 由于有且仅有一个实例,故可以减少内存开销;
  2. 可以避免对资源的多重占用,避免对同一资源进行多种操作;
  3. 设置了全局的资源访问点,可以优化和共享全局资源访问。

适用场景

  1. 应用中某个实例对象需要被频繁访问;
  2. 应用每次启动只需存在一个实例,如账号、数据库系统等。

单例模式几个关键点

  1. 构造函数不对外开放,一般为private
  2. 通过一个静态方法或者枚举返回单例类对象;
  3. 确保单例类的对象有且只有一个,尤其在多线程环境下;
  4. 确保单例类对象在反序列化时不会重新构建对象。

常用的使用方式

1、饿汉式(线程安全)

public class Singleton {

    // 内部定义自己的一个实例,只供内部调用
    private static final Singleton instance = new Singleton();

    // 私有构造方法,防止被实例化
    private Singleton() {
    }

    // 这里提供一个可供外部直接访问的静态方法
    public static Singleton getInstance() {
        return instance;
    }
}

优点:线程安全。
缺点:初始化的时候就实例化了。

2、懒汉式(线程不安全)

public class Singleton {

    // 私有静态实例,防止被引用此处赋值为null,目的是实现延迟加载
    private static Singleton instance = null;

    // 私有构造方法,防止被实例化
    private Singleton() {
    }

    // 静态方法创建实例
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

优点:延迟加载(使用的时候才被实例化),节省资源。
缺点:线程不安全,在多线程中很容易出现不同步的情况,如在数据库对象进行频繁的读写操作时。

为什么说它是线程不安全的呢?我们假设有两个线程,A线程和B线程都要初始化这个实例,此时A线程比较快,已经判断instancenull正在创建实例,而B线程这时候也再判断,但此时A线程还没有new完,instance还是为空的,所以B线程也开始new一个对象出来,这样就相当于创建了两个实例了,因此上面这种设计并不能保证线程是安全的。为了解决线程不安全问题我们可以加同步锁进行实现。

3、懒汉式(线程安全)

public class Singleton {

    // 私有静态实例,防止被引用此处赋值为null,目的是实现延迟加载
    private static Singleton instance = null;

    // 私有构造方法,防止被实例化
    private Singleton() {
    }

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

还可以如下方式写:

public class Singleton {

    // 私有静态实例,防止被引用此处赋值为null,目的是实现延迟加载
    private static Singleton instance = null;

    // 私有构造方法,防止被实例化
    private Singleton() {
    }

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

优点:线程安全。
缺点:每次调用实例都要判断同步锁,造成了不必要的同步开销,导致效率降低。

4、DCL双重检验锁(Double Check Lock 线程安全)

public class Singleton {

    private static Singleton instance = null;

    private Singleton() {
    }

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

上面我们没有用volatile进行修饰,它还是不安全的,有可能会出现null的问题。为什么呢?这是因为Javanew一个对象的时候是无序的。我们假设在这个过程中有线程A判断为null了,这个时候它就进入线程锁instance = new Singleton()了,但是它不是一蹴而就的,而是需要通过以下三步来完成:

  1. instance创建内存;
  2. new Singleton()调用构造方法;
  3. instance指向内存区域。

Java虚拟机在执行上面这三步的时候并不是按照此顺序来执行的,可能会打乱执行顺序,这就是Java的重排序,比如步骤23调换一下:

  1. instance创建内存;
  2. instance指向内存区域;
  3. new Singleton()调用构造方法。

此时instance已经指向内存区域了,那么它就不为null了,而实际上它并未获得构造方法。比如构造方法里面有些参数或者方法你并未获取,如果这个时候线程B过来,那么线程B在执行instance的某些方法时就会报错。虽然这种情况是非常少见的,不过还是暴露了这种问题的存在,所以我们需要用volatile进行修饰。

由于在JVM编译的过程中会出现指令重排的优化过程,这就可能会出现instance实际还没初始化的时候就有可能被分配了内存空间,也就是说可能会出现instance != null但是又没初始化的情况,这样就会导致返回的instance报错。在JDK 1.5之后,官方已经注意到这种问题,因此调整了JVM并具体化了volatile关键字,我们可以如下定义:

private volatile static Singleton instance = null;//使用volatile关键字

我们知道volatile的一个重要属性是可见性,即被volatile修饰的对象,在不同线程中是可以实时更新的。也就是说线程A修改了某个被volatile修饰的值,那么线程B也会知道它被修改了。同时它还有另一个禁止Java重排序的作用,这样我们就不用担心上面这种null的情况出现了。

但是这种方式依旧存在缺点:由于volatile关键字会屏蔽虚拟机中一些必要的代码优化,所以运行效率并不是很高。因此建议若没有特别的需要,不要大量使用。

优点:延迟加载,避免不必要的同步,线程安全。
缺点:在不同的编译环境下可能会出现不同的问题。
补充:Android图片加载开源项目Universal-Image-Loader中使用的就是这种方式。

5、内部类(线程安全)

public class Singleton {

    private static class SingletonHolder {
        private static Singleton INSTANCE = new Singleton();
    }

    private Singleton() {
    }

    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

优点:延迟加载,线程安全,减少内存开销。

6、枚举(线程安全)

public enum Singleton {

    SINGLETON;

    public void showMsg() {

    }
}

使用方式:

Singleton singleton = Singleton.SINGLETON;
singleton.showMsg();

优点:线程安全,延时加载,防止多次实例化,序列化和反序列化安全,即使是在反序列化的过程中枚举单例也不会重新生成新的实例。
缺点:从Java 1.5才开始支持。

7、静态式(线程安全)

public class Singleton {

    private Singleton() {
    }

    public static class Holder {

        private static Singleton instance = new Singleton();

        public static Singleton getInstance() {
            return instance;
        }
    }
}

如何进行参数传递呢?我们可以定义一个方法来实现。

public class Singleton {

    private Context mContext;

    private Singleton() {
    }

    public static class Holder {

        private static Singleton instance = new Singleton();

        public static Singleton getInstance() {
            return instance;
        }
    }

    public void init(Context context) {
        this.mContext = context;
    }
}

初始化:

Singleton mSingleton = Singleton.Holder.getInstance();
mSingleton.init(this);

优点:需要的时候再去初始化且能保证线程安全。
缺点:对于参数的传递比较不好。

结语:不管以哪种方式实现单例模式,它们的核心原理就是将构造函数私有化,并且通过静态公有方法获取一个唯一的实例,在这个获取的过程中必须保证线程的安全,同时也要防止反序列化导致重新生成实例对象。Android中常见单例模式的类有InputMethodManagerAccessibilityManagerEditable等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值