¿Son seguros los hilos de inicializadores estáticos de Java?

136

Estoy usando un bloque de código estático para inicializar algunos controladores en un registro que tengo. Mi pregunta es, por lo tanto, ¿puedo garantizar que este bloque de código estático solo se invocará una vez cuando se cargue la clase por primera vez? Entiendo que no puedo garantizar cuándo se llamará a este bloque de código, supongo que es cuando el cargador de clases lo carga por primera vez. Me doy cuenta de que podría sincronizar en la clase en el bloque de código estático, pero supongo que esto es lo que sucede de todos modos.

Ejemplo de código simple sería;

class FooRegistry {

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

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

o debería hacer esto;

class FooRegistry {

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

    private static void addController(IFooController controller) {
        // ...
    }
}
simon622
fuente
10
No me gusta este diseño, ya que no es comprobable. Echa un vistazo a la inyección de dependencia.
dfa

Respuestas:

199

Sí, los inicializadores estáticos de Java son seguros para subprocesos (use su primera opción).

Sin embargo, si desea asegurarse de que el código se ejecute exactamente una vez, debe asegurarse de que la clase solo sea cargada por un único cargador de clases. La inicialización estática se realiza una vez por cargador de clases.

Matthew Murdoch
fuente
2
Sin embargo, una clase puede ser cargado por múltiples cargadores de clase de manera AddController todavía puede quedar llamado más de una vez (independientemente de si está o no sincronizar la llamada) ...
Mateo Murdoch
44
Ah, espera, entonces, estamos diciendo que el bloque de código estático en realidad se llama para cada cargador de clases que carga la clase. Hmm ... Creo que esto todavía debería estar bien, sin embargo, im pregunto cómo ejecutar este tipo de código en un env OSGi funcionaría, con cargadores de clases de paquete haya numerosas ..
simon622
1
Si. Se llama al bloque de código estático para cada cargador de clases que carga la clase.
Matthew Murdoch
3
@ simon622 Sí, pero funcionaría en un objeto de clase diferente en cada ClassLoader. Diferentes objetos de clase que todavía tienen el mismo nombre completo, pero representan diferentes tipos que no se pueden convertir entre sí.
Erwin Bolwidt
1
¿Significa esto que la palabra clave 'final' es redundante en el titular de la instancia en: en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom ?
spc16670
11

Este es un truco que puedes usar para la inicialización diferida

enum Singleton {
    INSTANCE;
}

o para pre Java 5.0

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

Como el bloque estático en SingletonHolder se ejecutará una vez de manera segura para subprocesos, no necesita ningún otro bloqueo. La clase SingletonHolder solo se cargará cuando llame a instancia ()

Peter Lawrey
fuente
18
Estás basando esta respuesta en el hecho de que el bloque estático solo se ejecutará una vez a nivel mundial, que es la pregunta que se hizo.
Michael Myers
2
Creo que esto tampoco es seguro en el entorno de cargadores de clases múltiples, ¿qué dice?
Ahmad
2
@Ahmad Los entornos de cargadores de múltiples clases están diseñados para permitir que cada aplicación tenga sus propios singletons.
Peter Lawrey
4

En circunstancias habituales, todo en el iniciador estático sucede, antes de todo lo que usa esa clase, por lo que la sincronización no suele ser necesaria. Sin embargo, la clase es accesible para cualquier cosa que llame el intiailizador estático (incluso hacer que se invoquen otros inicializadores estáticos).

Una clase puede ser cargada por una clase cargada pero no necesariamente inicializada de inmediato. Por supuesto, una clase puede cargarse mediante múltiples instancias de cargadores de clases y, por lo tanto, convertirse en múltiples clases con el mismo nombre.

Tom Hawtin - tackline
fuente
3

Sí, más o menos

Un staticinicializador solo se llama una vez, por lo que, según esa definición, es seguro para subprocesos: necesitaría dos o más invocaciones del staticinicializador para incluso obtener la contención de subprocesos.

Dicho esto, los staticinicializadores son confusos en muchas otras formas. Realmente no hay un orden específico en el que se llaman. Esto se vuelve realmente confuso si tiene dos clases cuyos staticinicializadores dependen el uno del otro. Y si usa una clase pero no usa lo staticque configurará el inicializador, no está garantizado que el cargador de clases invoque el inicializador estático.

Finalmente, tenga en cuenta los objetos con los que está sincronizando. Me doy cuenta de que esto no es realmente lo que estás preguntando, pero asegúrate de que tu pregunta no sea realmente si necesitas hacer un addController()hilo seguro.

Mate
fuente
55
Hay un orden muy definido en el que se llaman: por orden en el código fuente.
mafu
Además, siempre se llaman, sin importar si usa su resultado. A menos que esto se haya cambiado en Java 6.
mafu
8
Dentro de una clase, los inicializadores siguen el código. Dadas dos o más clases, no está tan definido como qué clase se inicializa primero, si una clase se inicializa al 100% antes de que comience otra, o cómo se "entrelazan" las cosas. Por ejemplo, si dos clases tienen inicializadores estáticos que se refieren entre sí, las cosas se ponen feas rápidamente. Pensé que había formas que podría referirse a una static final int a otra clase w / o invocar a los inicializadores pero yo no voy a discutir el punto de una manera u otra
Matt
Se pone feo, y lo evitaría. Pero hay una forma definida de cómo se resuelven los ciclos. Citando "Java Language Programming 4th Edition": Página: 75, Sección: 2.5.3. Inicialización estática: "Si ocurren ciclos, los inicializadores estáticos de X se ejecutarán solo hasta el punto donde se invocó el método de Y. Cuando Y, a su vez, invoca el método de X, ese método se ejecuta con el resto de los inicializadores estáticos aún por ejecutar "
JMI MADISON
0

Sí, los inicializadores estáticos se ejecutan solo una vez. Lea esto para más información .

Mike Pone
fuente
2
No, se pueden ejecutar más de una vez.
Expiación limitada
55
No, se pueden ejecutar una vez POR CARGADOR DE CLASES.
ruurd
Respuesta básica: el inicio estático solo se ejecuta una vez. Respuesta avanzada: el inicio estático se ejecuta una vez por cargador de clase. El primer comentario es confuso porque las frases mezclan estas dos respuestas.
JMI MADISON
-4

Básicamente, dado que desea una instancia singleton, debe hacerlo más o menos a la antigua y asegurarse de que su objeto singleton se inicialice una vez y solo una vez.

ruurd
fuente