android的编程思想,Android系统编程思想篇:单例模式

本文围绕Android系统编程中的单例模式展开。介绍了单例模式的定义、常见使用场景、优缺点,详细阐述了懒汉式、双层校验锁、容器、静态内部类和枚举这5种实现方式及特点,还分析了双层校验锁可能出现的问题及解决办法,最后推荐了内部类和枚举单例,并给出开源项目应用示例。

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

Android系统编程思想篇:单例模式

关于作者

郭孝星,程序员,吉他手,主要从事Android平台基础架构方面的工作,欢迎交流技术方面的问题,可以去我的Github提issue或者发邮件至guoxiaoxingse@163.com与我交流。

单例模式可能是我们最常见的模式之一了,在单例模式中,我们要求一个系统只有一个全局对象存在,这样有利用我们去协调系统的整体行为。比如在某个服

务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置

信息。这种方式简化了在复杂环境下的配置管理。

模式定义

某个类有且只有一个实例,并且自行实例化,并向系统提供这个实例。

单例模式常见的使用场景

某个类的对象有且只有一个对象存在,这种场景分以下两种情况:

1 创建对象需要消耗大量资源,保证对象单一共享,减少资源消耗。

2 对象本身具有唯一性,不应该多次创建。

单例模式有哪些优缺点呢?

优点:

1 单例模式只有一个实例,减少类系统开销,避免了对资源的多重占用。

2 单例模式可以设置全局访问点,优化和共享资源访问。

缺点:

1 单例一般没有接口,后期做扩展比较困难,只能修改源码。

2 单例对象如果持有Context,容易引发内存泄漏,所以我们一般传递的是Application Context。

模式实现

单例模式实现要点:

1 构造函数不对外开放,一般为Private。

2 通过一个静态方法或者枚举类返回单例类对象。

3 确保单例类的对象有且只有一个,尤其要主要多线程环境下的线程安全问题,

3 确保单例类在反序列化时不会重新构建对象。

针对以上的实现要点,单例模式的实现一般说来有5种实现方式:

1 懒汉式单例

2 双层校验锁单例

3 容器单例

4 静态内部类单例

5 枚举单例

6种模式各有利弊,我们分别来看看这6种模式的具体实现方式以及它们的使用场景。

懒汉式单例

public class LazySingleton {

private static LazySingleton instance;

private LazySingleton() {

}

public synchronized static LazySingleton getInstance() {

if (instance == null) {

instance = new LazySingleton();

}

return instance;

}

}

优点:懒加载,使用的以后才会进行加载,节省资源。

缺点:每次调用时都会进行synchronized线程同步,消耗很多不必要的资源,实际上只需要在第一次创建对象的时候才会需要线程同步。

注:synchronized,Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。

双层校验锁单例

public class DoubleCheckSingleton {

private static DoubleCheckSingleton instance;

private DoubleCheckSingleton() {

}

public static DoubleCheckSingleton getInstance() {

//first check

if (instance == null) {

//double check

synchronized (DoubleCheckSingleton.class) {

instance = new DoubleCheckSingleton();

}

}

return instance;

}

}

优点:懒加载,同时由懒汉式单例扩展而来,只是在第一次创建实例的时候才会进行线程同步,节省了资源。

缺点:双层校验锁的方法由于Java内存模型的原因偶尔会失效。

我们来分析一下这个缺点。先来看看这句话"instance = new DoubleCheckSingleton()",这段代码最终会被编译成多条汇编指令,大致如下:

给DoubleCheckSingleton的实例分配内存空间

调用DoubleCheckSingleton()构造函数

将创建的instance对象指向分配的内存空间

假设有线程A和线程B,同时执行上述3条指令,由于JVM编译器允许处理器允许上述指令乱序执行,即上述第二条与第三条的顺序无法保证。想象以下这种情况:线程A异常执行过了第三条,此时

instance非空,线程B直接取走instance,因为instance虽然非空,但是并没有经过DoubleCheckSingleton()构造函数初始化,再使用时就会出错。

JDK1.5以后,通过volatile关键字来保证对象都是从主内存读取,来避免上述问题。

注:volatile,java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该确保通过排他锁单独获得这个变量。Java语言提供了volatile,在某些情况下比锁更加方便。如果

一个字段被声明成volatile,java线程内存模型确保所有线程看到这个变量的值是一致的。

public class DoubleCheckSingleton {

private volatile static DoubleCheckSingleton instance;

private DoubleCheckSingleton() {

}

public static DoubleCheckSingleton getInstance() {

//first check

if (instance == null) {

//double check

synchronized (DoubleCheckSingleton.class) {

instance = new DoubleCheckSingleton();

}

}

return instance;

}

}

容器单例

public class ContainerSingleton {

private static HashMap map = new HashMap<>();

private ContainerSingleton() {

}

public static void registerService(String key, Object service) {

if (!map.containsKey(key)) {

map.put(key, service);

}

}

public static Object getService(String key) {

return map.get(key);

}

}

优点:将多个单例类对象注入到统一的管理类中,使用统一的接口进行管理,隐藏类具体实现,降低了耦合度。

静态内部类单例

public class InnerClassSingleton {

private InnerClassSingleton() {

}

public static InnerClassSingleton getInstance() {

return InnerClassSingletonHolder.instance;

}

private static class InnerClassSingletonHolder {

private static final InnerClassSingleton instance = new InnerClassSingleton();

}

}

优点:懒加载,无需线程同步,没有性能问题。

缺点:好像没什么缺点

枚举单例

public enum EnumSingleton {

INSTANCE;

}

当当当,是不是炒鸡简单,这也是它最大的优点。

优点:写法简单,枚举的创建都是就是线程安全的,这也省去了线程同步的问题。而且它还解决了上述几种写法都不能解决的问题:"反序列化会导致重新创建实例"。

为什么反序列化会导致重新创建实例?

通过序列化可以讲一个单例的实例写到磁盘,然后再读回来,从而有效的获取一个单例的实例,即使这个单例的构造函数是私有的,反序列化中类里有个钩子函数readResolve(),

该函数可以控制对象的反序列化。

所以我们在上述几种单例的实现种,如果要避免反序列化重新创建实例,需要加上以下代码:

private Object readReslove() throws ObjectStreamException {

//将instance直接返回,不再重新生产实例

return instance;

}

好了,5种单例的实现方式都讲完了,我们来总结一下,不管是哪种实现方式,要点都在于:

1 构造函数不对外开放,一般为Private。

2 通过一个静态方法或者枚举类返回单例类对象。

3 确保单例类的对象有且只有一个,尤其要主要多线程环境下的线程安全问题,

3 确保单例类在反序列化时不会重新构建对象。

以上5种从性能、便利等角度考虑,推荐内部类单例和枚举单例。

模式实践

了解了单例的常见实现,我们来看一下常见的开源项目中都是如何使用单例的。

双层校验锁单例

public class ImageLoader {

private volatile static ImageLoader instance;

/** Returns singleton class instance */

public static ImageLoader getInstance() {

if (instance == null) {

synchronized (ImageLoader.class) {

if (instance == null) {

instance = new ImageLoader();

}

}

}

return instance;

}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值