---------------------- <a href="http://www.itheima.com"target="blank">ASP.Net+Unity开发</a>、<a href="http://www.itheima.com"target="blank">.Net培训</a>、期待与您交流! ----------------------
先来看看延时加载的单例代码:
- public static Singleton getInstance()
- {
- if (instance == null) //1
- instance = new Singleton(); //2
- return instance; //3
- }
由于以上没有采用同步,因此多线程环境下可能会创建多个实例。
那么我们加上synchronized,在方法中加了同步块:
- public static Singleton getInstance()
- {
- if (instance == null) //1
- {
- synchronized(Singleton.class) { //2
- instance = new Singleton();
- }
- }
- return instance; //3
- }
同步块包围了单例对象的创建, 看似这样就是个典型的延时加载的单例模式了。但还有一个问题,假设在线程A运行到//1的时候, 被线程B抢占了cpu,并运行到了//3,那么线程A继续执行就又会重复创建对象了,从而就破坏了单例的规则。
ok,那么我们再做一个修改,代码变成这样:
- public static Singleton getInstance()
- {
- if (instance == null) //1
- {
- synchronized(Singleton.class) {
- if (instance == null) //2
- instance = new Singleton(); //3
- }
- }
- return instance;
- }
我们看到, 除了//1进行了一次非null检查,在同步块代码//3处又加入了一次检查,这就是double-checked locking (DCL),这样就避免发生像上面的在其他线程又重复创建单例对象的问题,看起来好像是完美了。
不幸的是, 即使是DCL也可能会失败。
instance = new Singleton();
这么简单一行创建对象的代码,就分为几步:
1)Allocate memory,
2)invoke constructor,
3)give reference
如果这几步顺序就是1),2),3) 那没问题,不过JIT编译器不保证2)和3)的顺序, 于是可能变成了1),3),2) 看看这时可能会发生什么:
线程A先进入同步块,执行 instance = new Singleton(), 这时instance 只拿到了ref,但构造器还未执行,退出了同步块;
线程B切入, 这时instance 是 非null的(上面线程A已经分配了ref),于是就return了 instance,而这个instance 却是没有经过构造初始化的,再访问instance就会导致错误。
这就是JITcompilers的out-of-order问题, 导致了初始化和变量赋值的顺序不可预料,产生以上问题。
---------------------- <a href="http://www.itheima.com"target="blank">ASP.Net+Unity开发</a>、<a href="http://www.itheima.com"target="blank">.Net培训</a>、期待与您交流! ----------------------
详细请查看:<a href="http://www.itheima.com" target="blank">www.itheima.com</a>