双重检查锁定的由来
在Java程序中,有时需要推迟一些高开销的对象的初始化操作,并且只有在真正使用到这个对象的时候,才进行初始化,此时,就需要延迟初始化技术。
延迟初始化的正确实现是需要一些技巧的,否则容易出现问题,下面一一介绍。

方案1

public class UnsafeLazyInit{
private static Instance instance; public static Instance getInstance(){
if (instance == null){
instance = new Instance();
}
return instance;
}
}

这种做法的错误是很明显的,如果两个线程分别调用getInstance,由于对共享变量的访问没有做同步,很容易出现下面两种情况:
1.线程A和B都看到instance没有初始化,于是分别进行了初始化。
2.instance=new Instance操作被重排序,实际执行过程可能是:先分配内存,然后赋值给instance,最后再执行初始化。
如果是这样的话,其他线程可能就会读取到尚未初始化完成的instance对象。

方案2

public class UnsafeLazyInit{
private static Instance instance; public static synchronized Instance getInstance(){
if (instance == null){
instance = new Instance();
}
return instance;
}
}

这种做法的问题是很明显的,每一次读取instance都需要同步,可能会对性能产生较大的影响。

方案3

方案3是一个错误的双重检测加锁实现,看代码:

public class UnsafeLazyInit{
private static Instance instance; public static Instance getInstance(){
if (instance == null){
synchronized(UnsafeLazyInit.classs){
if (instance == null){
instance = new Instance();
}
}
}
return instance;
}
}

这种方案看似解决了上面两种方案都存在的问题,但是也是有问题的。

问题根源

instance = new Instance();

这一条语句在实际执行中,可能会被拆分程三条语句,如下:

memory = allocate();
ctorInstance(memory); //2
instance = memory; //3

根据重排序规则,后两条语句不存在数据依赖,因此是可以进行重排序的。
重排序之后,就意味着,instance域在被赋值了之后,指向的对象可能尚未初始化完成,而instance域是一个静态域,
可以被其他线程读取到,那么其他线程就可以读取到尚未初始化完成的instance域。

基于volatile的解决方案

要解决这个办法,只需要禁止语句2和语句3进行重排序即可,因此可以使用volatile来修改instance就能做到了。

private volatile static Instance instance;

因为Volatile语义会禁止编译器将volatile写之前的操作重排序到volatile之后。

基于类初始化的解决方案

Java语言规范规定,对于每一个类或者接口C ,都有一个唯一的初始化锁LC与之对应,从C到LC的映射,由JVM实现。
每个线程在读取一个类的信息时,如果此类尚未初始化,则尝试获取LC去初始化,如果获取失败则等待其他线程释放LC。
如果能获取到LC,则要判断类的初始化状态,如果是位初始化,则要进行初始化。如果是正在初始化,
则要等待其他线程初始化完成,如果是已经初始化,则直接使用此类对象。

public class InstanceFactory {
private static class InstanceHolder {
public static Instance instance = new Instance();
} public static Instance getInstance() {
return InstanceHolder.instance ; //这里将导致InstanceHolder类被初始化
}
}

结论

字段延迟初始化降低了初始化类或者创建实例的开销,但是增加零访问被延迟促使化的字段的开销。
在大部分时候,正常的初始化要优于延迟初始化。如果确实需要对实例字段使用线程安全的延迟初始化,
请使用上面介绍的基于volatile的延迟初始化方案;如果确实需要对静态字段使用线程安全的延迟初始化,
请使用上面基于类初始化方案的延迟初始化。

最新文章

  1. ie6 ie7 ie8 ie9兼容问题终极解决方案
  2. Excel 自动更正
  3. Java Swing
  4. putty不能连接linxu,报:connection refused
  5. FlashFXP 破解代码
  6. Daily Scrum – 1/11
  7. C#中的volatile用法
  8. web页面版权部分的显示问题
  9. JS对象类型的确定
  10. HTTP/1.1 Range和Content-Range
  11. hadoop 2.2.0 集群部署 坑
  12. php 查看文档
  13. guava中eventbus注解使用
  14. JavaScript编写了一个计时器
  15. 用DirectDraw封装的位图动画类
  16. 安卓高级8 SurfaceView (1)
  17. Luogu P2468 [SDOI2010]粟粟的书架
  18. Mvc_后端通用验证
  19. execl 导入
  20. Windows全版本KMS激活脚本

热门文章

  1. 微信小程序开发教程(一)准备
  2. HashSet如何排序
  3. Scrum实施调查案例
  4. Oracle 后台进程 详细说明
  5. Linux批量管理工具Ansible
  6. 解密所有APP运行过程中的内部逻辑(转)
  7. 如何判断一个请求是不是ajax请求
  8. 【JVM】调优笔记2-----JVM在JDK1.8以后的新特性以及VisualVM的安装使用
  9. 位图(BitMap)索引
  10. Jedis(Java+Redis) Pool的使用