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


136

我正在使用静态代码块来初始化我拥有的注册表中的某些控制器。因此,我的问题是,我可以保证在首次加载该类时,该静态代码块仅被绝对调用一次吗?我知道我不能保证何时会调用此代码块,我猜是在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) {
        // ...
    }
}

10
我不喜欢这种设计,因为它无法测试。看一下依赖注入。
dfa

Answers:


199

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

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


2
但是,一个类可以由多个类加载器加载,因此addController可能仍会被多次调用(无论您是否同步调用)……
Matthew Murdoch

4
嗯,等等,所以我们说静态代码块实际上是为每个加载该类的类加载器调用的。嗯......我想这应该仍然是好的,但是,即时知道如何在OSGi运行ENV这种代码会工作,与多张束类加载器..
simon622

1
是。对于每个加载该类的类加载器,都会调用静态代码块。
马修·默多克

3
@ simon622是的,但是它将在每个ClassLoader中的不同类对象中运行。不同的Class对象仍具有相同的完全限定名称,但表示无法强制转换的不同类型。
Erwin Bolwidt '16年

1
这是否意味着'final'关键字在以下实例所有者中是多余的:en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom
spc16670

11

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

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才会被加载


18
您将基于静态块仅在全局范围内执行一次这一事实来回答这个问题,这正是被问到的问题。
迈克尔·迈尔斯

2
我认为这在多类加载器环境中也不安全呢?
Ahmad 2012年

2
@Ahmad多类加载器环境旨在允许每个应用程序拥有自己的单例。
彼得·劳瑞

4

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

可以通过加载的类来加载类,但不必立即初始化。当然,一个类可由类加载器的多个实例加载,从而成为具有相同名称的多个类。


3

是的,有点

一个static初始化仅被调用一次,所以由定义,它的线程安全的-你需要在两个或两个以上的调用static初始化连得线程争。

也就是说,static初始化器在许多其他方面令人困惑。确实没有指定调用它们的顺序。如果您有两个其static初始化程序相互依赖的类,这将非常令人困惑。而且,如果您使用一个类但不使用static初始化程序将要设置的内容,则不能保证该类加载器将调用静态初始化程序。

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


5
有一个非常明确的顺序被调用:在源代码中按顺序。
马夫

此外,无论是否使用它们的结果,它们总是被调用。除非在Java 6中对此进行了更改
。– mafu

8
在一个类中,初始化器遵循代码。给定两个或多个类,它的定义并不是首先初始化哪个类,一个类是在另一个类开始之前100%初始化还是如何“交错”。例如,如果两个类各自具有互相引用的静态initalializers,那么事情很快就会变得丑陋。我以为有一些方法可以将静态final int引用到另一个类而无需调用初始化程序,但是我不会以一种或另一种方式争论这一点
Matt

它确实很难看,我会避免的。但是有一种解决循环的方法。引用“ Java编程语言第4版”:页面:75,部分:2.5.3。静态初始化:“如果发生循环,则X的静态初始化器将只在调用Y的方法时执行。当Y反过来调用X方法时,该方法将与其余尚未执行的静态初始化器一起运行“
JMI MADISON

0

是的,静态初始化器仅运行一次。 阅读此以获得更多信息


2
不,它们可以运行多次。
有限赎罪

5
否,它们只能按CLASS LOADER运行一次。
ruurd

基本答案:静态初始化仅运行一次。高级答案:静态初始化每个类加载器运行一次。第一条评论令人困惑,因为措辞将这两个答案混在一起。
JMI MADISON

-4

因此,基本上,既然您想要一个单例实例,则应或多或少地采用老式的方式进行操作,并确保将单例对象初始化一次且仅初始化一次。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.