Android中单例模式的一些坑小结中单例模式的一些坑小结
前言前言
单例模式最初的定义出现于《设计模式》(艾迪生维斯理, 1994):“保证一个类仅有一个实例,并提供一个访问它的全局访
问点。”
而我对单例的理解是,在可控的范围内充当全局变量的作用,就相当于C语言中一个全局结构体。
首先来看这样一个单例,稍微有点经验的同学可能都会说,这样的单例是非线程安全的。要加个volatile关键字才可以。
class Singleton{
private static Singleton singleton;
private Singleton(){};
public static Singleton getInstance()
{
if (singleton==null)
{
synchronized (Singleton.class)
{
if (singleton==null)
{
singleton=new Singleton();
}
}
}
return singleton;
}
}
但是你要是问他,为什么是非线程安全的单例就答不出来了。搞清楚这个问题其实 对我们的多线程理解是很有好处的。
我们首先明确一下对于jvm来说,完成对一个变量的写操作 到底是如何进行的。
写操作:写操作:
(1)先把值写入cpu的高速缓存cache中。(2)然后再把这个cache中的值拷贝到ram(也就是我们的内存)中。
注意啊注意啊,对于一个写操作来说,这个(1)(2) 可不是原子操作,很有可能(1)执行完毕以后,cpu又去干了其他事情,
并没有第一时间把cache的值 写入到ram中。而我们读操作,都是从ram中去读取一个值的。
所以这里我们可以想一下,如果是多线程场景的话,会有一些坑。
然后再说一个概念,对于 singleton=new Singleton(); 这一条语句来说,他显然不是一条指令就可以完成的。
正常情况来说,我们要完成这条语句涉及到的指令大约如下:
1.申请一段堆内存空间
2.在这个堆内存空间中把我们需要的对象初始化完毕
3.把singleton这个引用指向我们的堆内存空间地址。
但是坑爹就坑爹在,虚拟机会有一个指令重排序的概念。当虚拟机发现单线程下 指令的顺序变更不会导致结果异常的时候
就会触发指令重排序的机制, 他会导致上述的 123顺序发生变更,比如我们把顺序改成132 你就会发现 结果还是一样的。
(指令重排序的触发机制准确的来说是happens before原则 有兴趣的同学可以深挖)
如果发生如果发生132的执行顺序的执行顺序 会发生什么?会发生什么?
假设线程a 进入到了同步代码块中,这个时候触发了指令重排序,顺序变成132,假设cpu这个时候执行了13。然后转头
去执行线程b,线程b 进入getInstance方法的时候,他发现singleton 不是null了,于是欢天喜地的return了,
但是要知道这个时候线程a的 2还没执行,也就是说singleton虽然不是空,但是他指向的地址空间里面啥都没有,对象还没有
初始化。所以这是一个非常大的隐患,虽然他发生的概率极低,低到我现在都没有复现过这种现象,但是依旧有概率。
那么正确的写法:
class Singleton{
private static volatile Singleton singleton;
private Singleton(){};
public static Singleton getInstance()
{
if (singleton==null)
{