前言
如题,单例模式:用来创建出一个独一无二的,只有一个实例的对象。
应用场景
在线程池(Thread Pool),缓存(Cache),偏好设置,注册表,日志等这些情况下,都需要单一对象;
Android中,可能最常见的就是自己定义的 Base Application 了吧;其他的还有,第三方分享的统一入口,Event Bus的总线,消息机制中的 Message Queue;
另外还有一些具体的场景,比如在订外卖的时候,选择主食、配菜、酒水、餐具、送货地址...等等一个操作流程中,就存在一个唯一的订单对象;
其实,很多地方都有用,只是平常没有注意。
单例 VS 全局变量
- 实例的唯一性:
- 全部变量,可以在任意位置被重新赋值,存在着不可预料的风险
- 单例,在
getInstance()
中,无对象则创建,有则跳过,以此来保证唯一性
- 创建位置不确定:
- 全局变量,需要开发者团队协商,按照类别定义在不同的文件中
- 单例,
getInstance()
定义在该类的内部,引用位置明确
- 初始化时机和内存占用:
- 全局变量,声明时即初始化,内存全部分配
- 单例,可以延迟初始化,此时内存占用的很少,
PS:延迟初始化也是Android启动速度优化的一个点
创建的几种方式
按照单例的特性,我们可以总结一下创建的步骤:
1)需要一个类,且只能在该类内部初始化,这样就保证了单例对象不会再其他地方被重新赋值,转化成代码就是,将构造器私有化,像这样 private Singleton() {...}
;
2)私有化的构造器,无法像 new Singleton()
这样直接调用,于是乎,就有了 public static Singleton getInstance() {...}
这段代码;
3)到这里为止,创建就完成了,接下来我们需要保证唯一性,所以就需要一个静态对象来保持引用 private static Singleton uniqueInstance
;
4)最简单的单例就完成了,基本代码如下:
public class Singleton {
private static Singleton uniqueInstance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return uniqueInstance;
}
}
PS:当然以上代码真的是不忍直视(其实也并不正确),接下来我们一点一点优化它。
1,添加懒加载的懒汉式
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
PS:这段代码简单明了,而且使用了懒加载模式,但是却存在致命的问题。当有多个线程并行调用 getInstance() 的时候,就会创建多个实例。也就是说在多线程下不能正常工作。
2,线程安全的懒汉式:添加 synchronized
关键字修饰 getInstance()
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
PS:虽然做到了线程安全,并且解决了多实例的问题,但是它并不高效。因为在任何时候只能有一个线程调用 getInstance() 方法。但是同步操作只需要在第一次调用时才被需要,即第一次创建单例实例对象时。这就引出了双重检验锁。
3,双重检验锁
会有两次检查 instance == null,一次是在同步块外,一次是在同步块内。为什么在同步块内还要再检验一次?因为可能会有多个线程一起进入同步块外的 if,如果在同步块内不进行二次检验的话就会生成多个实例了。
public static Singleton getSingleton() {
if (instance == null) { // Single Checked
synchronized (Singleton.class) {
if (instance == null) { // Double Checked
instance = new Singleton();
}
}
}
return instance ;
}
PS: 将 instance
变量声明成 volatile
,是可以更好的解决线程安全的问题,关于这点,暂且不表。
4,线程安全的单例最简单实现:饿汉式 static final field
public class Singleton{
// 类加载时就初始化
private static final Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
PS:这种写法,简单而粗暴,但是并不完美,原因有二。1)不是懒加载;2)无法设置参数。
5,静态内部类 static nested class
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
PS:这种写法仍然使用 JVM
本身机制保证了线程安全问题;由于 SingletonHolder
是私有的,除了 getInstance()
之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本。
6,最简洁的单例:枚举
public enum EasySingleton{
INSTANCE;
}
PS:没有比这更简单的了~~~
总结
一般来说,单例模式有五种写法:懒汉式、饿汉、双重检验锁、静态内部类、枚举。个人会倾向于使用静态内部类的方式,相对会万金油。
网友评论