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

发布于 2024-12-04 08:36:33 字数 267 浏览 2 评论 0原文

我正在观看 Java 内存模型视频演示,作者说与延迟初始化相比,使用静态延迟初始化更好,我不清楚他想说什么。

我想接触社区,如果有人能用简单的 java 代码示例解释静态延迟初始化延迟初始化之间的区别,我将不胜感激。

参考:高级编程主题 - Java 内存模型

I am going through Java Memory Model video presentation and author is saying it is better to use Static Lazy Initialization compared to Lazy Initialization and I do not clear understand what he wants to say.

I wanted to reach to community and would appreciate if someone can explain difference between Static Lazy Initialization and Lazy Initialization with simple java code example.

Reference: Advanced Programming Topics - Java Memory Model

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

胡大本事 2024-12-11 08:36:33

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

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

下面是一个线程安全的静态初始化对象的示例

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 是什么。不仅如此,每次读取都将是正常的,无需担心线程安全。我个人喜欢我列出的第一个例子。

Well both implementations can be static so that is the first misunderstanding. The presenter in this video is explaining how you can exploit the thread-safety of class initialization.

Class initialization is inherently thread-safe and if you can have an object initialized on class initialization the object creation too are thread-safe.

Here is an example of a thread-safe statically initialized object

public class MySingletonClass{

   private MySingletonClass(){

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

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

   }

}

What is important to know here, MySingletonClass instance variable will never be created and or initialized until getInstance() is invoked. And again since class initialization is thread-safe the instance variable of IntiailizationOnDemandClassholder will be loaded safely, once and is visible to all threads.

To answer your edit depends on your other type of implementation. If you want to do double-checked-locking your instance variable would need to be volatile. If you do not want DCL then you will need to synchronize access each time to your variable. Here are the two examples:

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

and

public class ThreadSafeLazySingleton{
   private static ThreadSafeLazySingleton instance;

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

}

The last example requires a lock acquisition on every request of the instance. The second example requires a volatile-read on each access (may be cheap or not, depends on the CPU).

The first example will always lock once regardless of the CPU. Not only that but each read will be a normal without any need to worry about thread-safety. I personally like the first example I have listed.

和我恋爱吧 2024-12-11 08:36:33

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

class StaticLazyExample1 {

   static Helper helper = new Helper();

   static Helper getHelper() {
      return helper;
   }
}

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

还有一种按需初始化持有者习惯用法,它基于静态延迟初始化:

class StaticLazyExample2 {

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

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

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

更新

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

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

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

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

I think the author in the presentation refers to the fact that a static field would be initialized only once in a thread-safe way at the first use of the class which contains that field (this is guaranteed by JMM):

class StaticLazyExample1 {

   static Helper helper = new Helper();

   static Helper getHelper() {
      return helper;
   }
}

Here helper field would be initialized upon first usage of StaticLazyExample1 class (i.e. upon constructor or static method call)

There is also Initialization On Demand Holder idiom, which is based on static lazy initialization:

class StaticLazyExample2 {

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

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

Here a Helper instance would be created only upon first call to StaticLazyExample2.getHelper() static method. This code is guaranteed to be thread-safe and correct because of the initialization guarantees for static fields; if a field is set in a static initializer, it is guaranteed to be made visible, correctly, to any thread that accesses that class.

UPDATE

What is the difference between both types of initialization?

The static lazy initialization provides efficient thread safe lazy initialization of the static fields and has zero synchronization overhead.
On the other hand if you would like to lazily initialize a non-static field, you should write something like this:

class LazyInitExample1 {

  private Helper instance;

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

Or use Double-Cheked Locking idiom:

class LazyInitExample2 {

    private volatile Helper helper;

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

Should I mention they both require explicit synchronization and carry additional timing overhead comparing to static lazy initialization?

小瓶盖 2024-12-11 08:36:33

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

enum ThreadSafeLazyLoadedSingleton {
    INSTANCE;
}

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

我认为你不能说一个比另一个更好,因为它们实际上不能互换。

It is worth noting that the simplest thread safe static lazy initialisation is to use an enum This works because initialisation of static fields is thread safe and classes are lazily loaded anyway.

enum ThreadSafeLazyLoadedSingleton {
    INSTANCE;
}

A class which uses a lazy loaded value is String. The hashCode is only computed the first time it is used. After that the cached hashCode is used.

I don't think you can say that one is better than the other because they are not really interchangeable.

-小熊_ 2024-12-11 08:36:33

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

A reference would be good here, for sure. They both have the same basic idea: Why allocate resources (memory, cpu) if you don't have to? Instead, defer allocation of those resources until they're actually needed. This can be good in intensive environments to avoid waste, but can be very bad if you need the results right now and cannot wait. Adding a "lazy but prudent" system is very difficult (one that detects downtime and runs these lazy calculations when it gets free time.)

Here's an example of lazy initialization.

class Lazy {

    String value;
    int computed;

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

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

}

Here's static lazy initializtion

class StaticLazy {

    private StaticLazy staticLazy;
    static StaticLazy getInstance() {
        if(staticLazy == null) staticLazy = new StaticLazy();
        return staticLazy;
    }
}
在你怀里撒娇 2024-12-11 08:36:33

区别在于实现延迟初始化的机制。通过静态延迟初始化,我假设演示者的意思是这个解决方案依赖于 JVM 与任何版本的 Java 兼容(请参阅 Java 语言规范的 12.4 类和接口的初始化)。

延迟初始化可能意味着这个问题的许多其他答案中描述的延迟初始化。这种初始化机制对 JVM 做出的假设在 Java 5 之前都不是线程安全的(因为 Java 5 具有真正的内存模型规范)。

The distinction is the mechanism you implement the lazy initialization. By Static Lazy Initialization I assume the presenter means this solution which relies on the JVM being compliant with any version of Java (see 12.4 Initialization of Classes and Interfaces, of the Java Language Specification).

Lazy Initialization probably means lazy initialization described in many other answers to this question. Such initialization mechanisms make assumptions about the JVM that are not thread-safe until Java 5 (as Java 5 has a real memory model specification).

只有一腔孤勇 2024-12-11 08:36:33
  1. 延迟加载只是一个奇特的名称,用于在实际需要时初始化类的过程。

  2. 简单来说,延迟加载是一种软件设计模式,其中对象的初始化仅在实际需要时才发生,而不是在此之前,以保持使用的简单性并提高性能。

  3. 当对象创建的成本非常高并且对象的使用非常罕见时,延迟加载就至关重要。所以这种情况下就值得实现延迟加载。延迟加载的基本思想是在需要时加载对象/数据。

资料来源: https://www.geeksforgeeks.org/lazy-loading-design-pattern /

  1. Lazy loading is just a fancy name given to the process of initializing a class when it’s actually needed.

  2. In simple words, Lazy loading is a software design pattern where the initialization of an object occurs only when it is actually needed and not before to preserve simplicity of usage and improve performance.

  3. Lazy loading is essential when the cost of object creation is very high and the use of the object is very rare. So this is the scenario where it’s worth implementing lazy loading.The fundamental idea of lazy loading is to load object/data when needed.

Source: https://www.geeksforgeeks.org/lazy-loading-design-pattern/

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文