线程:惰性初始化与静态惰性初始化

zu0ti5jz  于 2022-10-22  发布在  Java
关注(0)|答案(6)|浏览(206)

我正在看Java内存模型视频演示,作者说使用Static Lazy InitializationLazy Initialization更好,我不清楚他想说什么。
我想联系社区,希望有人能用简单的java代码示例解释Static Lazy InitializationLazy Initialization之间的区别。
参考:Advanced Programming Topics - Java Memory Model

7cwmlq89

7cwmlq891#

这两种实现都可能是静态的,所以这是第一个误解。本视频中的演示者将解释如何利用类初始化的线程安全性。
类初始化本质上是线程安全的,如果可以在类初始化时初始化对象,则对象创建也是线程安全的。
下面是线程安全静态初始化对象的示例

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

}

最后一个示例要求在示例的每个请求上获取锁。第二个示例要求在每次访问时进行易失性读取(可能便宜或不便宜,取决于CPU)。
第一个示例将始终锁定一次,而与CPU无关。不仅如此,每次读取都是正常的,无需担心线程安全。我个人喜欢我列举的第一个例子。

0pizxfdo

0pizxfdo2#

我认为演示文稿中的作者提到了这样一个事实,即在第一次使用包含该字段的类时,静态字段只会以线程安全的方式初始化一次(这由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()静态方法时创建。由于静态字段的初始化保证,此代码保证线程安全且正确;如果在静态初始值设定项中设置了一个字段,则可以保证该字段对访问该类的任何线程都是可见的。

更新

这两种类型的初始化有什么区别?
静态惰性初始化为static字段提供了高效的线程安全惰性初始化,并且同步开销为零。另一方面,如果您想懒洋洋地初始化非静态字段,您应该编写如下代码:

class LazyInitExample1 {

  private Helper instance;

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

或者使用双重检查锁定习语:

class LazyInitExample2 {

    private volatile Helper helper;

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

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

n6lpvg4x

n6lpvg4x3#

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

enum ThreadSafeLazyLoadedSingleton {
    INSTANCE;
}

使用惰性加载值的类是String。hashCode仅在首次使用时计算。之后使用缓存的hashCode。
我不认为你可以说一个比另一个好,因为它们不是真正可互换的。

yzckvree

yzckvree4#

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

agyaoht75#

区别在于实现惰性初始化的机制。通过Static Lazy Initialization,我假设演示者意味着这个解决方案,它依赖于JVM与任何版本的Java兼容(参见Java语言规范的12.4类和接口的初始化)。
Lazy Initialization可能意味着这个问题的许多其他答案中描述的惰性初始化。这种初始化机制假设JVM在Java5之前是线程安全的(因为Java5有一个真正的内存模型规范)。

r8xiu3jd

r8xiu3jd6#

简单地说,对象创建是根据需求完成的

相关问题