双重检查锁定和延迟初始化

双重检查锁定的由来

创新互联-专业网站定制、快速模板网站建设、高性价比成武网站开发、企业建站全套包干低至880元,成熟完善的模板库,直接使用。一站式成武网站制作公司更省心,省钱,快速模板网站建设找我们,业务覆盖成武地区。费用合理售后完善,十载实体公司更值得信赖。

在Java程序中,有时需要推迟一些高开销的对象的初始化操作,并且只有在真正使用到这个对象的时候,才进行初始化,此时,就需要延迟初始化技术。

延迟初始化的正确实现是需要一些技巧的,否则容易出现问题,下面一一介绍。

方案1

 
 
  1. public class UnsafeLazyInit{
  2. private static Instance instance;
  3.   public static Instance getInstance(){
  4.     if (instance == null){
  5.          instance = new Instance();
  6.      }
  7.      return instance;
  8.  }
  9. }  

这种做法的错误是很明显的,如果两个线程分别调用getInstance,由于对共享变量的访问没有做同步,很容易出现下面两种情况:

1.线程A和B都看到instance没有初始化,于是分别进行了初始化。

2.instance=new Instance操作被重排序,实际执行过程可能是:先分配内存,然后赋值给instance,***再执行初始化。如果是这样的话,其他线程可能就会读取到尚未初始化完成的instance对象。

方案2

 
 
  1. public class UnsafeLazyInit{
  2. private static Instance instance;
  3. public static synchronized Instance getInstance(){
  4.     if (instance == null){
  5.          instance = new Instance();
  6.      }
  7.      return instance;
  8.  }
  9. }

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

方案3

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

 
 
  1. public class UnsafeLazyInit{
  2. private static Instance instance;
  3. public static Instance getInstance(){
  4.     if (instance == null){
  5.          synchronized(UnsafeLazyInit.classs){
  6.              if (instance == null){
  7.                   instance = new Instance();
  8.                }
  9.           }
  10.      }
  11.      return instance;
  12.   }
  13. }

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

问题根源

instance = new Instance();

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

 
 
  1. memory = allocate();
  2. ctorInstance(memory); //2
  3. 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,则要判断类的初始化状态,如果是位初始化,则要进行初始化。如果是正在初始化,则要等待其他线程初始化完成,如果是已经初始化,则直接使用此类对象。

 
 
  1. public class InstanceFactory{
  2.     private static class InstanceHolder{
  3.         public static Instance = new Instance();
  4.      }
  5.      
  6.     public static Instance getInstance(){
  7.         return InstanceHolder.instance; //这里将导致instance类被初始化
  8.     }    
  9. }

结论

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

当前名称:双重检查锁定和延迟初始化
分享路径:http://www.gawzjz.com/qtweb/news3/187003.html

网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联