线程化:延迟初始化与静态延迟初始化

时间:2011-09-14 17:33:49

标签: java multithreading static lazy-initialization

我正在阅读Java Memory Model视频演示,作者说使用Static Lazy InitializationLazy Initialization相比更好,我不清楚他想说什么。

我想与社区联系,如果有人能用简单的java代码示例解释Static Lazy InitializationLazy Initialization之间的区别,我会很感激。

参考:Advanced Programming Topics - Java Memory Model

5 个答案:

答案 0 :(得分:18)

两个实现都可以是静态的,这是第一个误解。本视频中的演示者正在解释如何利用类初始化的线程安全性。

类初始化本质上是线程安全的,如果你可以在类初始化时初始化对象,那么对象创建也是线程安全的。

以下是线程安全的静态初始化对象

的示例
public class MySingletonClass{

   private MySingletonClass(){

   }
   public static MySingletonClass getInstance(){
         return IntiailizationOnDemandClassholder.instance;
   }

   private static class IntiailizationOnDemandClassHolder{
         private static final MySingletonClass instance = new MySingletonClass();

   }

}

重要的是要知道,在调用getInstance()之前,永远不会创建和/或初始化MySingletonClass实例变量。同样,由于类初始化是线程安全的,instance IntiailizationOnDemandClassholder变量将被安全地加载,一次并且对所有线程都可见。

回答您的编辑取决于您的其他类型的实施。如果你想进行双重检查锁定,你的实例变量需要是易失性的。如果您不想要DCL,则每次都需要同步访问变量。以下是两个例子:

public class DCLLazySingleton{
  private static volatile DCLLazySingleton instance;

  public static DCLLazySingleton getInstace(){
     if(instance == null){
        synchronized(DCLLazySingleton.class){
            if(instance == null)
                instance=new DCLLazySingleton();
        }
     } 
     return instance;
}

public class ThreadSafeLazySingleton{
   private static ThreadSafeLazySingleton instance;

  public static ThreadSafeLazySingleton getInstance(){
     synchronized(ThreadSafeLazySingleton.class){
        if(instance == null){
            instance = new ThreadSafeLazySingleton();
        }
        return instance;
     } 

}

最后一个示例要求在实例的每个请求上获取锁。第二个例子需要在每次访问时进行易失性读取(可能很便宜或不依赖,取决于CPU)。

无论CPU如何,第一个示例将始终锁定一次。不仅如此,每次读取都是正常的,无需担心线程安全。我个人喜欢我列出的第一个例子。

答案 1 :(得分:1)

肯定会在这里提及参考。它们都有相同的基本思想:如果你不需要,为什么要分配资源(内存,cpu)?相反,推迟分配这些资源,直到实际需要它们为止。这在密集环境中可以很好地避免浪费,但如果您现在需要结果并且不能等待,则可能非常糟糕。添加“懒惰但谨慎”的系统非常困难(可以检测停机时间并在空闲时间运行这些懒惰的计算。)

这是延迟初始化的一个例子。

class Lazy {

    String value;
    int computed;

    Lazy(String s) { this.value = s; }

    int compute() {
        if(computed == 0) computed = value.length();
        return computed;
    }

}

这是静态懒惰初始化

class StaticLazy {

    private StaticLazy staticLazy;
    static StaticLazy getInstance() {
        if(staticLazy == null) staticLazy = new StaticLazy();
        return staticLazy;
    }
}

答案 2 :(得分:1)

我认为演示文稿中的作者提到静态字段在第一次使用包含该字段的类时以线程安全的方式仅初始化一次(这由JMM保证):

class StaticLazyExample1 {

   static Helper helper = new Helper();

   static Helper getHelper() {
      return helper;
   }
}

helper字段将在首次使用StaticLazyExample1类时初始化(即在构造函数或静态方法调用时)

还有Initialization On Demand Holder习惯用法,它基于静态延迟初始化:

class StaticLazyExample2 {

  private static class LazyHolder {
    public static Helper instance = new Helper();
  }

  public static Helper getHelper() {
    return LazyHolder.instance;
  }
}

这里只有在第一次调用Helper静态方法时才会创建StaticLazyExample2.getHelper()实例。由于静态字段的初始化保证,此代码保证是线程安全且正确的。如果在静态初始化程序中设置了一个字段,则可以保证它可以正确地显示给访问该类的任何线程。

<强>更新

  

两种类型的初始化有什么区别?

静态延迟初始化为静态字段提供了高效的线程安全延迟初始化,并且没有同步开销。 另一方面,如果您想懒洋洋地初始化非静态字段,您应该写下这样的内容:

class LazyInitExample1 {

  private Helper instance;

  public synchronized Helper getHelper() {
    if (instance == null) instance == new Helper();
    return instance;
  }
}

或使用Double-Cheked Locking成语:

class LazyInitExample2 {

    private volatile Helper helper;

    public Helper getHelper() {
      if (helper == null) {
          synchronized (this) {
              if (helper == null) helper = new Helper();
          }
      }
      return helper;
    }
}

我应该提一下,与静态延迟初始化相比,它们都需要显式同步并承载额外的时序开销吗?

答案 3 :(得分:1)

值得注意的是,最简单的线程安全静态延迟初始化是使用enum这是有效的,因为静态字段的初始化是线程安全的,而且无论如何都会延迟加载类。

enum ThreadSafeLazyLoadedSingleton {
    INSTANCE;
}

使用延迟加载值的类是String。 hashCode仅在第一次使用时计算。之后使用缓存的hashCode。

我不认为你可以说一个比另一个好,因为它们不是真的可以互换。

答案 4 :(得分:0)

区别在于您实现延迟初始化的机制。通过Static Lazy Initialization我假设演示者意味着this solution依赖于JVM符合任何版本的Java(see 12.4 Initialization of Classes and Interfaces, of the Java Language Specification)。

Lazy Initialization可能意味着在此问题的许多其他答案中描述的延迟初始化。这样的初始化机制假设JVM在Java 5之前是非线程安全的(因为Java 5具有真实的内存模型规范)。