一、简介
单例模式是最简单也比较常见常用的设计模式。我们通常建立一个类,通过该类来创建实例,而有时候,我们仅仅需要这个类的一个实例就够了,例如各种工具类,其实现的效果都是一样的,我没有必要创建多个实例来占用系统资源。
定义:单例模式是一种将类的实例化限制为一个对象的设计模式。
使用场景:各种工具类,例如日志打印等,缓存和线程池,数据库连接等。
实现要素:
- 限制其只有一个实例。
- 全局都可以访问该实例。
二、实现
单例模式的实现方式有很多种。
1、经典实现
//Java 代码
class Singleton {
private static Singleton obj;
private Singleton() {}
public static Singleton getInstance() {
if (obj==null)
obj = new Singleton();
return obj;
}
}
//Kotlin 代码
class Singleton {
private constructor()
companion object{
private var obj: Singleton? = null
val instance: Singleton
get() {
if (obj == null) obj = Singleton()
return obj!!
}
}
}
这里我们对 Java 代码稍微分析一下,先看看有一个私有化的构造方法:private Singleton() {}
,这段代码避免了你在其他地方可以通过new Singleton()
来创建实例。
然后定义一个私有化的Singleton实例,和一个初始化Singleton的实例并返回该实例的方法:getInstance()
。
我们的预想是可以通过Singleton.getInstance()
来获取实例,所以将getInstance()
这个方法设置为静态函数,由于该方法时静态方法,那么里面的相关属性也要成为静态变量,所以才有了上面的代码。
2、加一层安全
为了防止多个线程同时第一次访问,而造成的线程安全问题,我们要多加一个同步锁。
//Java 代码
... ...
public static synchronized Singleton getInstance()
{
if (obj==null)
obj = new Singleton();
return obj;
}
... ...
//Kotlin 代码
... ...
val instance: Singleton
@Synchronized
get() {
if (obj == null) obj = Singleton()
return obj!!
}
... ...
3、急切实例化
//Java 代码
class Singleton {
private static Singleton obj = new Singleton();
private Singleton() {}
public static Singleton getInstance()
{
return obj;
}
}
这里在定义Singleton实例的时候,直接就初始化实例。因为虚拟机加载程序的时候,会先初始化静态值,所以能够保证线程是安全的。但是,最好不要这么做,如果你的类有一些静态变量,且在私有构造方法里面会有一些操作静态变量的代码,很可能会出现意想不到的后果。因为代码的执行循序可能不是你想的那样。
4、双重检查(推荐)
class Singleton {
private volatile static Singleton obj;
private Singleton() {}
public static Singleton getInstance()
{
if (obj == null)
{
synchronized (Singleton.class)
{
if (obj==null)
obj = new Singleton();
}
}
return obj;
}
}
使用 volatile 关键词,保证了实例的原子性。那么为什么要加两层判断呢?
试想一下,当实例已经被初始化,我们还需要同步嘛?我们可以直接将实例拿过来使用,如果还是同步的话,那就是大大的浪费系统资源。通过双重检查,仅当第一次初始化实例的时候,才会同步,之后就不需要同步了。
5、Kotlin实现
Kotlin 给我们提供了一个关键词 object,这个关键词可以作为类的修饰,被修饰的类直接就是单例模式了。
object Singleton{
}
当我们查看该 Kotlin 代码的字节码,再转换成 Java 代码,就是如下所示:
public final class Singleton{
public static final Singleton INSTANCE;
private Singleton() {
}
static {
Singleton var0 = new Singleton();
INSTANCE = var0;
}
}
6、其他实现
只要基于单例模式的原则,可以变着花样的实现。
- 我们甚至可以用 枚举 来创建对象,在 Java 中,enum 和 class、interface 是同一个级别的,枚举天生就是一个单例。
- 我们还可以利用一个 Map 集合,以 key&value 的形式,每一个 key 对应一个对象的方式来储存一个单例,当我们需要使用到某个单例,就通过 key 从 Map 中获取这个对象。
三、相关源码
Glide 作为当下比较流行的图片加载框架,想必大家都不会很陌生。而图片加载工具,我们往往只需要一个实例,在任何地方,只需要获得该实例来进行图片的加载,我们复习一下Glide如何使用:
Glide.with(context).load(url).into(imageView)
我们查看一下Glide的部分源码:
private static volatile Glide glide;
//构造方法
Glide(... ...) {
... ...
}
@NonNull
public static Glide get(@NonNull Context context) {
if (glide == null) {
synchronized (Glide.class) {
if (glide == null) {
checkAndInitializeGlide(context);
}
}
}
return glide;
}
其使用了双重检查的方法,来进行实现单例模式的。有个小不同的就是其构造方法,并没有用 private 修饰,但是也没有使用 public 修饰,这儿就表明了其封装的包内类是可以初始化 Glide 实例,而我们还是无法 new 一个实例。
四、小结
单例模式的使用场景还是比较多的,其实现也比较简单,本着学会一个用一个的想法,积极在项目中使用到,相信也是对整体代码起到了优化作用。不过一定要贴合具体需求,来看看自己是否需要使用该设计模式。
在 Android 里面,有两个地方会使得单例模式失效:
- 多进程
- 反序列化
具体为什么,我们完全可以预想的到。多进程意味着有完全多个资源区,每个进程都会产生一个单例;而当我们序列化了一个单例类,产生了一个序列化的文件,我们可以通过该文件进行多次反序列化,也会产生多个相同的实例。