一.定义:
确保某一个类只有一个实例,而且自行实例并向整个系统提供这个实例

二.使用场景:
避免产生过多的对象消耗过多的资源,或者某种类型的对象只应该有且只有一个。例如,创建一个对象需要消耗的资源过多,如访问数据库或者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);
    }
}