Android设计模式详解之单例模式

本文详细介绍了Java中的单例模式,包括饿汉模式、懒汉模式、DCL双检锁模式和静态内部类实现。强调了单例模式在资源管理和线程安全方面的应用,并通过Android源码展示了系统服务的单例实现。此外,还探讨了单例模式的优缺点以及在实际开发中的注意事项。
摘要由CSDN通过智能技术生成

前言

定义:确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

使用场景:确保某个类有且仅有一个对象的场景,避免产生多个对象消耗过多的资源。比如要访问IO和数据库资源,应该考虑使用单例模式。

UML类图:
单例UML图

实现方式

饿汉模式

/**
 * 饿汉单例
 */
public class Singleton {
    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {

    }

    public static Singleton getInstance() {
        return INSTANCE;
    }

}

优点:对象优先创建,无须等待,效率高。
缺点:申明静态对象的时候就已经初始化,一定程度上造成了资源的浪费。

懒汉模式

/**
 * 懒汉单例
 */
public class Singleton {
    private static Singleton instance;

    private Singleton() {

    }

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

        return instance;
    }

}

优点:只有在使用时才会被实例化,一定程度上节约了资源。
缺点:第一次加载时需要等待,同时每一次调用getInstance()都进行同步,造成不必要的同步开销。

DCL(Double Check Lock)单例

/**
 * DCL单例
 */
public class Singleton {
    private volatile static Singleton instance;

    private Singleton() {

    }

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

}

volatile关键字的作用:

  1. 禁止指令重排序;
    instance = new Singleton();这句代码实际上并不是一个原子操作,最终会被编译成多条汇编指令,大致做了如下3件事;
  • (1)给Singleton的实例分配内存;
  • (2)调用Singleton()的构造函数,初始化成员字段;
  • (3)将instance对象指向分配的内存空间(此时instance不再为null)

但是Java编译器允许处理器乱序执行,以上的执行顺序可能是1-2-3也可能是1-3-2,如果是后者,在线程A中当3执行完毕2未执行时,如果切换到线程B,此时instance已经非空,线程B直接取走instance,在使用就会出错,这就会导致DCL失效!!

  1. 线程可见性,及时更新实例到主内存;【JMM Java内存模型】

getInstance()方法中两次判空的作用:

第一次:避免重复加锁,造成资源浪费;
第二次:避免对象重复实例化;

优点:资源利用率高,避免不必要的同步;
缺点:第一次加载效率低,在某些情况下会出现DCL失效问题

静态内部类单例

/**
 * 静态内部类单例
 */
public class Singleton {
    private Singleton() {

    }

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

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

}

优点:懒加载,能够确保线程安全,推荐使用这种单例模式实现方式!

容器实现单例模式

/**
 * 容器实现单例模式
 * on 2022/12/20
 */
public class SingletonManager {
    private static Map<String, Object> singletonMap = new HashMap<>();

    private SingletonManager() {
    }

    public static void registerService(String key, Object instance) {
        if (!singletonMap.containsKey(key)) {
            singletonMap.put(key, instance);
        }
    }

    public static Object getService(String key) {
        return singletonMap.get(key);
    }
}

优点:可以管理多种类型的单例,使用时可以通过统一的接口进行获取操作,降低用户使用成本,也对用户隐藏了具体实现,降低耦合度。

Android源码中的单例模式

  1. Context.getSystemService(String name)

这段代码我们经常使用去获取各种系统service,其实底层的具体实现就使用到了容器单例模式,接下来我们就跟踪下源码,看下具体实现;
我们知道Context是抽象类,他的实现类是ContextImpl,其中ContextImpl.getSystemService(String name)方法如下:

    public Object getSystemService(String name) {
		...
        return SystemServiceRegistry.getSystemService(this, name);
    }

我们继续分析SystemServiceRegistry.getSystemService(this, name)

    public static Object getSystemService(ContextImpl ctx, String name) {
        if (name == null) {
            return null;
        }
        final ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        if (fetcher == null) {
        	...
            return null;
        }

        final Object ret = fetcher.getService(ctx);
        if (sEnableServiceNotFoundWtf && ret == null) {
			...
            return null;
        }
        return ret;
    }

重点看下SYSTEM_SERVICE_FETCHERS实例

		...
    private static final Map<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
            new ArrayMap<String, ServiceFetcher<?>>();
		...
 static {
 ...
   registerService(Context.ACTIVITY_SERVICE, ActivityManager.class,
                new CachedServiceFetcher<ActivityManager>() {
            @Override
            public ActivityManager createService(ContextImpl ctx) {
                return new ActivityManager(ctx.getOuterContext(), ctx.mMainThread.getHandler());
            }});

        registerService(Context.ACTIVITY_TASK_SERVICE, ActivityTaskManager.class,
                new CachedServiceFetcher<ActivityTaskManager>() {
            @Override
            public ActivityTaskManager createService(ContextImpl ctx) {
                return new ActivityTaskManager(
                        ctx.getOuterContext(), ctx.mMainThread.getHandler());
            }});
	...
 }
        
	//注册service
 private static <T> void registerService(@NonNull String serviceName,
            @NonNull Class<T> serviceClass, @NonNull ServiceFetcher<T> serviceFetcher) {
        SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
        SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
        SYSTEM_SERVICE_CLASS_NAMES.put(serviceName, serviceClass.getSimpleName());
    }    

可以看到SYSTEM_SERVICE_FETCHERS是一个静态的ArrayMap实例,在static静态代码块中,对系统中的各个服务进行注册,如我们熟悉的ACTIVITY_SERVICEACTIVITY_TASK_SERVICELAYOUT_INFLATER_SERVICE等等,使用的时候根据serviceNameArrayMap中查找到对应Service,
显然,这里的各个服务是单例的!~~

  1. LayoutInfater实例单例

我们经常使用LayoutInfater去绑定布局文件,如LayoutInflater.from(this).inflate(),但实际上LayoutInflater
对象也是个单例的,接下来我们就从源码的角度去验证;
LayoutInflater是个抽象类,他的实现类是PhoneLayoutInflater,我们还是回过头看下SystemServiceRegistry

static{
    registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
                new CachedServiceFetcher<LayoutInflater>() {
            @Override
            public LayoutInflater createService(ContextImpl ctx) {
                return new PhoneLayoutInflater(ctx.getOuterContext());
            }});
}

在静态代码块中同样是实例化了全局唯一的PhoneLayoutInflater对象,而我们调用的地方:

 public static LayoutInflater from(Context context) {
        LayoutInflater LayoutInflater =
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (LayoutInflater == null) {
            throw new AssertionError("LayoutInflater not found.");
        }
        return LayoutInflater;
    }

正是通过LAYOUT_INFLATER_SERVICE去查找对应的Service对象;

总结

单例模式是运用频率很高的模式;

它有以下优点:

  1. 内存中只有一个实例,减少了内存开销;
  2. 可以避免对资源的多重占用;
  3. 可以全局共享资源,方便数据访问;

同时他的缺点也显而易见:

  1. 一般没有接口,扩展性较差,扩展只能通过修改代码来实现;
  2. 如果持有Context,容易引发内存泄漏,注意传递给单例对象的Context应为Application Context

结语

如果以上文章对您有一点点帮助,希望您不要吝啬的点个赞加个关注,您每一次小小的举动都是我坚持写作的不懈动力!ღ( ´・ᴗ・` )

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值