个人博客
单例模式
模式介绍
整个系统只需要拥有一个全局对象,这样有利于我们协调系统整体的行为。
定义
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
实现单例模式主要有如下几个关键点:
-
构造函数不对外开放,一般为private
-
通过一个静态方法或者枚举返回单例类对象
-
确保单例类的对象有且只有一个,尤其是在多线程环境下
-
确保单例类对象在反序列化时不会重新构建对象
单例模式的写法
- 饿汉式
在声明静态对象时已经初始化。
public class Singleton {
private static final Singleton mInstance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return mInstance;
}
}
- 懒汉式
懒汉模式是声明一个静态对象,并且在用户第一次调用getInstance时进行初始化。优点是:单例只在使用时才会被实例化。缺点是:第一次加载需要及时进行实例化,反应稍慢,每次调用时都进行同步,造成不必要的同步开销。这种模式不建议使用。
public class Singleton {
private static volatile Singleton mInstance;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (mInstance == null) {
mInstance = new Singleton();
}
return mInstance;
}
}
- DCL(Double Check Lock)实现单例
DCL模式实现单例的优点是既能够在需要时才初始化单例,又能够保证线程安全,且单例对象初始化后调用getInstance不进行同步锁。
public class Singleton {
private static volatile Singleton mInstance;
private Singleton() {
}
public static Singleton getInstance() {
if (mInstance == null) {
synchronized (Singleton.class) {
if (mInstance == null) {
mInstance = new Singleton();
}
}
}
return mInstance;
}
}
- 静态内部类单例模式
当第一次加载Singleton类时并不会初始化sInstance,只有在第一次调用Sington的getInstance方法时才会导致sInstance被初始化。这是推荐使用的单例模式实现方式。
public class Singleton {
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.sInstance;
}
private static class SingletonHolder {
private static final Singleton sInstance = new Singleton();
}
}
- 枚举单例
在上述的几种单例模式实现中,在反序列化的情况下,它们就会出现重新创建对象。
序列化可以将一个单例的实例对象写到磁盘,然后再读回来,从而有效地获得一个实例。即使构造函数是私有的,反序列化时依然可以通过特殊的途径去创建一个新的实例。反序列化操作提供了一个特别的钩子函数,类中具有一个私有的readResolve()函数,这个函数可以让开发人员控制对象的反序列化。如果要杜绝对象在反序列化时重新生成对象,必须加入readResolve函数。而枚举则不存在这个问题。
private Object readResolve() throws ObjectStreamException {
return mInstance;
}
对于序列化,有两点需要注意:
-
可序列化类中的字段类型不是Java的内置类型,那么该字段也需要实现Serializable接口
-
如果调整了可序列化类的内部结构,如新增,去除某个字段,但没有修改serialVersionUID,那么会引发java.io.InvalidClassException异常或者导致某个属性为0或者null。此时的最好方案是直接将serialVersionUID设置为0L,这样即使修改类的内部结构,反序列化也不会报错,只是新修改的字段会为0或者null。
写法简单是枚举单例的最大优点。枚举在Java中与普通的类是一样的,不仅能够有字段,还能够有自己的方法。最重要的是默认枚举实例的创建是线程安全的,并且在任何情况下它都是一个单例。
public enum Singleton {
INSTANCE;
public void doSomething()
{
}
}
- 使用容器实现单例模式
public class SingletonManager<T> {
private static Map<String, Object> objMap = new HashMap<>();
private SingletonManager() {
}
public static void registerService(String key, Object instance) {
if (!objMap.containsKey(key)) {
objMap.put(key, instance);
}
}
public static Object getService(String key) {
return objMap.get(key);
}
}
在程序的初始阶段,将多种单例类型注入到一个统一的管理类中,在使用时根据key获取对象。这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低了用户的使用成本,也对用户隐藏具体实现,降低耦合度。