Java 为什么不';我们不需要同步一个懒惰的init getter(holder习惯用法)?

Java 为什么不';我们不需要同步一个懒惰的init getter(holder习惯用法)?,java,multithreading,lazy-initialization,Java,Multithreading,Lazy Initialization,我正在阅读J.Bloch的有效Java,现在我在读关于延迟初始化的部分。考虑下面的类: public class LazyInit{ public static getObject(){ //Not synchronized return Holder.o; } private static Object createObject(){ System.out.println("Creating started"); r

我正在阅读J.Bloch的有效Java,现在我在读关于延迟初始化的部分。考虑下面的类:

public class LazyInit{

    public static getObject(){  //Not synchronized
        return Holder.o;
    }

    private static Object createObject(){
        System.out.println("Creating started");
        return new Object();
    }

    private static class Holder{
        private static Object o = createObject();
    }
}
布洛赫谈到这个成语:

这个成语的妙处在于getField方法不是 已同步并且只执行字段访问,所以很懒 初始化实际上不会增加访问成本


我不明白为什么它能安全通过。如果在字段初始化期间,另一个线程尝试同时访问该字段,该怎么办?在线程需要该对象时,该对象不会被创建。那么,接下来会发生什么呢?

这种方法基于JVM的初始化原则

只有在加载
Holder
类之后,才会创建
对象的实例。在应用程序中第一次引用字段
o
时,类加载器将执行
Holder
类的加载(这里是
getObject
方法)。类加载是非当前的,因此此模式保证:

  • 对象
    实例将按需创建(延迟初始化)
  • 创建
    对象
    将是线程安全的
  • 摘自:

    由于JLS保证类初始化阶段是串行的,即非并发的,因此在加载和初始化期间,静态getInstance方法中不需要进一步同步


    根据评论更新: @圣安塔里奥发现了这一特征:

    初始化C的过程如下:同步C的初始化锁LC。这包括等待当前线程可以获取LC


    不清楚。那么,如果在一个线程初始化类的过程中,另一个线程尝试访问它的字段,会发生什么呢。它会被锁定直到初始化完成吗?@St.Antario,这就是诀窍。对象实例将在Holder类初始化期间创建,并且Holder类是非并发初始化的,这意味着第二个线程将和第一个线程一样等待Holder类初始化。实际上,得到了它。下面是我发现的一些:初始化C的过程如下:同步C的初始化锁LC。这包括等待当前线程可以获取LC。如果您在答案中添加了一些正式的引用,可能会对某些人有用。因为类初始化是非并发的,当初始化很昂贵并且可以并行完成时,这也可能是一种反模式。