一.定义:
确保某一个类只有一个实例,而且自行实例并向整个系统提供这个实例
二.使用场景:
避免产生过多的对象消耗过多的资源,或者某种类型的对象只应该有且只有一个。例如,创建一个对象需要消耗的资源过多,如访问数据库或者IO 资源。
三.实现单例模式的关键点:
1.私有化构造方法
2.通过静态方法或枚举返回单例类对象
3.确保单例类的对象有且只有一个,尤其在多线程下
4.确保单例对象在反序列化时不会重新构建对象
四.实现方式
1.Double Check Lock(DCL)实现模式
public class SingleInstance
{
private static volatile SingleInstance sInstance = null;
private SingleInstance()
{
}
public static SingleInstance getInstance()
{
if (sInstance == null)
{
synchronized (SingleInstance.class)
{
if (sInstance == null)
{
sInstance = new SingleInstance();
}
}
}
return sInstance;
}
}那么这里为啥判空两次呢?
第一次判空是为了避免不必要的同步,第二次判空则是为了在null的情况下创建实例。
分析:sInstance=new SingleInstance()语句并不是一个原子操作,代码会被编译成多条编译指令,这里大致做了三件事情
(1)给SingleInstance的实例分配内存
(2)调用SingleInstance的构造方法SingleInstance(),初始化成员字段
(3)将sInstance的对象指向分配的内存空间(此时sInstance就不是null了)
但是,由于java编译器允许处理器乱序执行,以及jdk1.5之前的JMM(Java Memory Model)的Cache,寄存器到主存回写顺序的规定,上面的(2)(3)的顺序是不确定的,也就是说执行顺序可能是1-3-2,1-2-3,如果是前者,(3)执行完(2)还未执行,切换到B线程,这时候sInstance已经是非空的了,B这时候直接取走sInstance,使用的时候就会出错,这就是DCL失效的原因。
jdk1.5之后,sun公司意识到这个问题,调整了JVM,具体化了volatile关键字,可以保证sInstance每次都是从主存中取。
2.静态内部类单例模式:
public class SingleInstance{
private SingleInstance(){}
public static SingleInstance getInstance(){
return SingleInstanceHolder.sInstance;
}
private static class SingleInstanceHolder{
private static final SingleInstance sInstance=new SingleInstance();
}
}第一次加载SingleInstance 并不是初始化sInstance,只有在第一次调用getInstance方法才会导致sInstance的初始化,这种方式能确保线程安全,单例对象唯一性。
3.枚举类型
public enum SingleInstance{
INSTANCE;
public void doSomething(){`这里写代码片`
}
}线程安全,任何时候都是单一实例。
在上述的几种实现方式中,在反序列化的情况下他们会出现重新创建对象。为什么?
序列化可以将一个单例的实例对象写到磁盘,然后在读出来,从而有效的获得一个实例。即使构造方法是私有的,反序列化仍可以通过一个特殊途径创建一个新的实例,,相当于调用构造方法,反序列化提供了一个特别的钩子函数,可以控制对象的反序列化。在上述的示例中,如果杜绝对象在反序列化时重新生成对象,必须加入readResolve函数。枚举则不会出现这种问题
public class SingleInstance implements Serializable
{
private static final long serialVersionUID = 0L;
private static volatile SingleInstance sInstance = null;
private SingleInstance()
{
}
public static SingleInstance getInstance()
{
if (sInstance == null)
{
synchronized (SingleInstance.class)
{
if (sInstance == null)
{
sInstance = new SingleInstance();
}
}
}
return sInstance;
}
private Object readResolve() throws ObjectStreamException
{
return sInstance;
}
}三点注意:
1.单例中如果需要Context,最好传入ApplicationContext,否则很容易发生内存泄漏。
2.可序列化的字段类型不是Java内置模型,这个字段也要实现Serializable.
3.如果你调整了可序列化类的内部结构,没有修改serialVersionUID,就会抛出异常,最好的方案是将serialVersionUID设置为0L,这样即使改 了类的内部结构,不会抛出异常,只是修改的字段为0或者null。
四.使用容器类实现单例模式
public class SingleInstanceManager
{
private static Map<String, Object> sObjectMap = new HashMap<>();
private SingleInstanceManager()
{
}
public static void registerService(String key, Object instance)
{
if (!sObjectMap.containsKey(key))
{
sObjectMap.put(key, instance);
}
}
public static Object getService(String key)
{
return sObjectMap.get(key);
}
}
















