天天看点

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

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

双重检查锁定的由来

在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 static synchronized instance getinstance(){ 

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

方案3

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

public static instance getinstance(){ 

         synchronized(unsafelazyinit.classs){ 

             if (instance == null){ 

                  instance = new 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 = new instance(); 

    public static instance getinstance(){ 

        return instanceholder.instance; //这里将导致instance类被初始化 

    }     

结论

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

作者:humc

来源:51cto