【问题标题】:Threading : Lazy Initialization vs Static Lazy Initialization线程:延迟初始化与静态延迟初始化
【发布时间】:2011-11-17 05:49:38
【问题描述】:

我正在浏览 Java 内存模型视频演示,作者说使用 Static Lazy Initialization 比使用 Lazy Initialization 更好,我不清楚他想说什么。

我想接触社区,如果有人能用简单的 java 代码示例解释 Static Lazy InitializationLazy Initialization 之间的区别,我将不胜感激。

参考:Advanced Programming Topics - Java Memory Model

【问题讨论】:

  • 错误,视频时长 57 分钟。这当然很有趣,但您或许可以告诉我们视频中讨论这个特定主题的位置。
  • 从第 43 分钟到第 48 分钟看,这 5 分钟讨论了静态延迟初始化。

标签: java multithreading static lazy-initialization


【解决方案1】:

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

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

这里是一个线程安全的静态初始化对象的例子

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 实例变量。同样,由于类初始化是线程安全的,IntiailizationOnDemandClassholderinstance 变量将被安全加载一次并且对所有线程可见。

回答您的编辑取决于您的其他类型的实施。如果您想进行双重检查锁定,您的实例变量将需要是易失的。如果您不想要 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;
     } 

}

最后一个示例需要在实例的每个请求上获取锁。第二个示例需要在每次访问时进行 volatile-read(可能便宜或不便宜,取决于 CPU)。

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

【讨论】:

  • 这两种初始化有什么区别?
  • 我正在检查您的编辑,究竟什么是双重检查锁定?
  • @Rachel 双重检查锁定将首先检查对象是否已初始化,如果未初始化,则会锁定类实例以进行创建。然后它将再次检查对象是否已创建,否则将创建它。由于它持有锁,它将确保只创建一个实例。 DLC 的问题在于,如果字段不是易失性的,它就不能保证安全的对象初始化。如果你喜欢cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html,你可以在这里阅读更多内容
  • @JohnVint 请注意您的 DCL 版本已损坏。看到这个:ibm.com/developerworks/library/j-dcl/index.html
  • @artaxerxe 你能更详细地解释一下为什么它相对于链接被破坏了吗?
【解决方案2】:

我认为演示文稿中的作者提到了这样一个事实,即在第一次使用包含该字段的类时,静态字段只会以线程安全的方式初始化一次(这是由 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;
  }
}

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

更新

这两种初始化有什么区别?

静态延迟初始化为 static 字段提供高效的线程安全延迟初始化,并且同步开销为零。 另一方面,如果你想懒惰地初始化一个 non-static 字段,你应该这样写:

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;
    }
}

与静态延迟初始化相比,我是否应该提到它们都需要显式同步并带来额外的时间开销?

【讨论】:

  • 这两种初始化有什么区别?
  • 什么是静态延迟初始化?我无法理解这个概念。
  • Static Lazy Initialization 是静态字段的惰性初始化。正如演示文稿所说:If you need to initialize a singleton value, something that will only be initialized once, lazily, per JVM - initialize it in the declaration of a static variable。 Java 规范保证it will be initialized in a thread safe way at the first use of the class, but not before。有关静态字段的静态延迟初始化示例,请参阅我的前两个示例(StaticLazyExample 类)。
  • 而且值得一提的是,你不能使用 Static Lazy Initializationnon-static 字段进行惰性初始化,只能用于静态那些。
  • 好的,所以在你上面的两个例子中,第一个例子不是线程安全的,对吧?
【解决方案3】:

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

enum ThreadSafeLazyLoadedSingleton {
    INSTANCE;
}

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

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

【讨论】:

  • 我想了解您的答案,您能否详细说明这两种方法之间的区别,我特别想了解延迟初始化和静态延迟初始化之间的实际区别是什么也不确定演示者在解释静态延迟初始化时的意思是什么?
  • AFAIK,静态延迟初始化是静态字段的延迟初始化。广义的延迟初始化是任何延迟初始化。 (这可能包括静态的)我很想对非静态字段使用术语非静态延迟初始化。您可能会区分这些的原因是它们有不同的用途。例如单例总是静态的。
【解决方案4】:

当然,在这里提供参考会很好。他们都有相同的基本思想:如果不需要,为什么要分配资源(内存、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;
    }
}

【讨论】:

  • 你也能解释一下区别吗?
  • 如果我正确理解了您的 cmets,那么为什么要进行静态延迟初始化,似乎总是应该选择延迟初始化。
  • Youtube 在这里工作时被屏蔽了,所以很遗憾我无法观看您的视频以了解讲师的原因。就个人而言,我看不出它们之间有什么区别。视频中的讲师可能和我有不同的意见,或者更可能是定义上的不同。我会试着看看我是否记得稍后再看这些,也许我可以回复你。
  • 当然,从第43分钟到第48分钟,这5分钟试图解释静态延迟初始化,也无法理解两种延迟初始化的区别,请您更贴切地解释区别?
【解决方案5】:

区别在于您实现延迟初始化的机制。 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 有一个真正的内存模型规范)。

【讨论】:

    猜你喜欢
    • 2020-05-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-08
    • 2017-11-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多