Java静态初始化器线程安全吗?

Posted

技术标签:

【中文标题】Java静态初始化器线程安全吗?【英文标题】:Are Java static initializers thread safe? 【发布时间】:2010-10-27 02:05:51 【问题描述】:

我正在使用静态代码块来初始化我拥有的注册表中的一些控制器。因此,我的问题是,我可以保证这个静态代码块在第一次加载类时绝对只会被调用一次吗?我知道我无法保证何时调用此代码块,我猜是 Classloader 第一次加载它的时间。我意识到我可以在静态代码块中的类上同步,但我猜这实际上是怎么回事?

简单的代码示例是;

class FooRegistry 

    static 
        //this code must only ever be called once 
        addController(new FooControllerImpl());
    

    private static void addController(IFooController controller)  
        // ...
    

或者我应该这样做;

class FooRegistry 

    static 
        synchronized(FooRegistry.class) 
            addController(new FooControllerImpl());
        
    

    private static void addController(IFooController controller) 
        // ...
    

【问题讨论】:

我不喜欢这种设计,因为它无法测试。看看依赖注入。 【参考方案1】:

是的,有点

static 初始化程序只被调用一次,因此根据该定义,它是线程安全的——您需要两次或多次调用 static 初始化程序才能获得线程争用。

也就是说,static 初始化器在许多其他方面都令人困惑。实际上没有指定的调用顺序。如果您有两个类的 static 初始化程序相互依赖,这会变得非常混乱。如果你使用一个类但不使用 static 初始化器将设置的东西,你不能保证类加载器会调用静态初始化器。

最后,记住要同步的对象。我意识到这并不是您真正要问的,但请确保您的问题不是真正询问您是否需要使 addController() 线程安全。

【讨论】:

有一个非常明确的调用顺序:在源代码中按顺序。 此外,无论您是否使用它们的结果,它们总是被调用。除非这在 Java 6 中有所改变。 在一个类中,初始化器遵循代码。给定两个或更多类,它没有定义为首先初始化哪个类,一个类是否在另一个类开始之前 100% 初始化,或者事物如何“交错”。例如。如果两个类每个都有相互引用的静态初始化器,那么事情会很快变得丑陋。我认为有一些方法可以将静态 final int 引用到另一个类而无需调用初始化程序,但我不会以某种方式争论这一点 它确实变得丑陋,我会避免它。但是对于如何解决周期,有一个明确的方法。引用“Java 编程语言第 4 版”:第 75 页,第 2.5.3 节。静态初始化:“如果发生循环,X 的静态初始化程序将只执行到调用 Y 的方法的点。当 Y 反过来调用 X 方法时,该方法将与其余的静态初始化程序一起运行,但尚未执行"【参考方案2】:

所以基本上,既然你想要一个单例实例,你应该或多或少地用老式的方式来做,并确保你的单例对象只初始化一次。

【讨论】:

【参考方案3】:

这是一个可以用于延迟初始化的技巧

enum Singleton 
    INSTANCE;

或用于 Java 5.0 之前的版本

class Singleton 
   static class SingletonHolder 
      static final Singleton INSTANCE = new Singleton();
   
   public static Singleton instance() 
      return SingletonHolder.INSTANCE;
   

由于 SingletonHolder 中的静态块将以线程安全的方式运行一次,因此您不需要任何其他锁定。只有在调用 instance() 时才会加载 SingletonHolder 类

【讨论】:

您的回答是基于静态块只会在全局范围内执行一次这一事实——这正是被问到的问题。 我认为这在多类加载器环境中也不安全怎么说。? @Ahmad 多类加载器环境旨在允许每个应用程序拥有自己的单例。 不需要嵌套类。当 INSTANCE 字段直接在 Singleton 中声明时,此构造的工作方式相同(enum 变体就是这种情况)。【参考方案4】:

是的,Java 静态初始化器是线程安全的(使用您的第一个选项)。

但是,如果您想确保代码只执行一次,您需要确保该类仅由单个类加载器加载。每个类加载器执行一次静态初始化。

【讨论】:

但是,一个类可以由多个类加载器加载,因此 addController 可能仍会被多次调用(无论您是否同步调用)... 啊,等一下,所以我们说静态代码块实际上会为每个加载类的类加载器调用。?嗯......我想这应该还可以,但是,我想知道如何在 OSGI 环境中运行这种代码,使用多个捆绑类加载器.. 是的。每个加载类的类加载器都会调用静态代码块。 @simon622 是的,但它会在每个 ClassLoader 的不同类对象中运行。不同的 Class 对象仍然具有相同的完全限定名称,但表示不能相互转换的不同类型。 这是否意味着'final'关键字在实例持有者中是多余的:en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom?【参考方案5】:

是的,静态初始化程序只运行一次。 Read this for more information.

【讨论】:

不,它们可以运行多次。 不,每个类加载器只能运行一次。 基本答案:静态初始化只运行一次。高级答案:静态初始化每个类加载器运行一次。第一条评论令人困惑,因为措辞混合了这两个答案。【参考方案6】:

在通常情况下,静态初始化程序中的所有内容都发生在使用该类的所有内容之前,因此通常不需要同步。但是,静态初始化程序调用的任何东西都可以访问该类(包括导致调用其他静态初始化程序)。

一个类可以被一个类加载,但不一定要立即初始化。当然,一个类可以被多个类加载器实例加载,从而成为多个同名类。

【讨论】:

以上是关于Java静态初始化器线程安全吗?的主要内容,如果未能解决你的问题,请参考以下文章

C++11中的局部静态变量初始化线程安全吗? [复制]

java 局部静态变量在多线程环境下是不是有线程安全问题??

SecureRandom 线程安全吗?

静态初始化不安全调用的线程安全

局部静态的线程安全初始化:MSVC [重复]

java线程安全问题之静态变量实例变量局部变量