在我们实际的开发过程中,最经常用到的设计模式,应该属于—— 单例模式 了吧。
一、定义
它是这样的定义的:
保证一个类仅有一个实例,并提供一个访问它的全局访问入口。
它的定义也非常简单,我们可以稍微整理下:
如上图,其实定义就两个点:仅一个实例、确保它是唯一的。
二、实现
上面简单描述了单例模式的定义,但在实际实现的过程中,大概需要注意以下几点:
1)构造函数私有化。如果构造函数公开的化,就容易使得该对象存在另外被实例化的可能,从而破坏了单利模式的全局(当前进程)唯一性;
2)单例对象的线程安全;
3)反序列化时,单实例也不应该被重建。
2.1 常用实现方式
2.1.1 饿汉式
public class Singleton {
private Singleton() {}
private static final Singleton mInstance = new Singleton();
public static Singleton getInstance() {
return mInstance;
}
}
2.1.2 懒汉式
public class Singleton {
private Singleton() {}
private static Singleton mInstance = null;
public static synchronized Singleton getInstance() {
if (mInstance == null) {
mInstance = new Singleton();
}
return mInstance;
}
}
2.1.3 双重锁方式
public class Singleton {
private Singleton() {}
private static volatile Singleton mInstance = null;
public static Singleton getInstance() {
if (mInstance == null) {
synchronized (Singleton.class) {
if (mInstance == null) {
mInstance = new Singleton();
}
}
}
return mInstance;
}
}
双重锁的方式使用了锁和双重判断的方法来实现目标单例的线程安全,也是我们经常用的方式,也挺满足我们上面要求的,看似挺靠谱的。
实际情况呢,可能你在面试的时候会被问到,通过DCL的方式来实现单例靠谱吗?其实并不很靠谱。
DCL的方式是存在失效的情况(尽管概率很小):
1) 当执行 mInstance = new Singleton() 这句操作时,实际上并不是原子操作的,即对象非空的时候,对象的成员变量可能还未初始化完成,此时就出现失效的情况;
2) 由于java内存模型可能会有失效的情况,可以使用关键字volatile(可确保所有线程看到该属性的值是一致的)。当然,使用找个关键字是有性能代价的。
DCL方式小结:
1、优点:首次使用时才会被实例化,大多数情况下能达到线程安全的目的;
2、缺点:高并发的时候可能存在上述失效情况,由于加了锁原因,第一次的时候会相对较慢。
2.2 较靠谱的方式
2.2.1 静态内部类
在上面常用的实现方式中,我们也提到了它的优缺点,那是否有更好实现方式呢?答案是有的。
在此介绍一种通过静态内部类的方式来实现单例模式,并能满足我们上面提到的诉求:
public class Singleton {
private Singleton() {}
public static Singleton getInstance() {
return SingletonObj.mInstance;
}
private static class SingletonObj {
private static final Singleton mInstance = new Singleton();
}
}
特点:
1)mInstance实例只有当getInstance被调用时才会被实例化,从而达到实例延迟加载的目的;
2)内部类SingletonObj开始被调用,就会实例mInstance对象,从而达到线程安全的目的,该内部类和上述的饿汉式类似。
2.2.2 枚举的方式
这种方式是在一本书上看到的(《Android源码设计模式》,推荐),实现看起来非常简单优雅:
public enum SingletonEnum {
/** single instance of enum **/
INSTANCE;
}
看起来是不是非常简单,不仅能实现我们想要的单例形式,也能做到实例的线程安全。
而且这种方式有个特点就是支持反序列化场景,即反序列化的时候不会出现重新创建对象的情况。
三、小结
以上就是实现单例的方式,基本是实现静态方法获取单实例,并做到实例的线程安全、以及反序列化单实例情况。
如果有更好实现的方式,欢迎补充。