Les initialiseurs statiques Java sont-ils sûrs pour les threads?

136

J'utilise un bloc de code statique pour initialiser certains contrôleurs dans un registre que j'ai. Ma question est donc la suivante: puis-je garantir que ce bloc de code statique ne sera absolument appelé qu'une seule fois lors du premier chargement de la classe? Je comprends que je ne peux pas garantir quand ce bloc de code sera appelé, je suppose que c'est quand le Classloader le charge pour la première fois. Je me rends compte que je pourrais synchroniser sur la classe dans le bloc de code statique, mais je suppose que c'est en fait ce qui se passe de toute façon?

Un exemple de code simple serait;

class FooRegistry {

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

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

ou devrais-je faire cela;

class FooRegistry {

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

    private static void addController(IFooController controller) {
        // ...
    }
}
simon622
la source
10
Je n'aime pas ce design, car il n'est pas testable. Jetez un œil à Injection de dépendances.
dfa

Réponses:

199

Oui, les initialiseurs statiques Java sont thread-safe (utilisez votre première option).

Cependant, si vous voulez vous assurer que le code est exécuté exactement une fois, vous devez vous assurer que la classe n'est chargée que par un seul chargeur de classe. L'initialisation statique est effectuée une fois par chargeur de classe.

Matthieu Murdoch
la source
2
Cependant, une classe peut être chargée par plusieurs chargeurs de classe, donc addController peut toujours être appelé plus d'une fois (que vous synchronisiez ou non l'appel) ...
Matthew Murdoch
4
Ah attendez, alors nous disons que le bloc de code statique est en fait appelé pour chaque chargeur de classe qui charge la classe.? Hmm ... Je suppose que cela devrait toujours être correct, cependant, je me demande comment exécuter ce type de code dans un environnement OSGI fonctionnerait, avec plusieurs chargeurs de classes
groupés
1
Oui. Le bloc de code statique est appelé pour chaque chargeur de classe qui charge la classe.
Matthew Murdoch
3
@ simon622 Oui, mais cela fonctionnerait dans un objet de classe différent dans chaque ClassLoader. Différents objets de classe qui ont toujours le même nom complet, mais qui représentent différents types qui ne peuvent pas être convertis les uns aux autres.
Erwin Bolwidt
1
cela signifie-t-il que le mot-clé «final» est redondant dans le détenteur de l'instance dans: en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom ?
spc16670
11

Ceci est une astuce que vous pouvez utiliser pour une initialisation paresseuse

enum Singleton {
    INSTANCE;
}

ou pour pré Java 5.0

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

Comme le bloc statique de SingletonHolder s'exécutera une fois de manière thread-safe, vous n'avez besoin d'aucun autre verrouillage. La classe SingletonHolder ne sera chargée que lorsque vous appelez instance ()

Peter Lawrey
la source
18
Vous basez cette réponse sur le fait que le bloc statique ne sera exécuté qu'une seule fois globalement - ce qui est la question même qui a été posée.
Michael Myers
2
Je pense que cela n'est pas non plus sûr dans un environnement de chargeur multi-classes que dire.?
Ahmad
2
@Ahmad Les environnements de chargement multi-classes sont conçus pour permettre à chaque application d'avoir ses propres singletons.
Peter Lawrey
4

Dans des circonstances habituelles, tout dans l'initialiseur statique se produit - avant tout ce qui utilise cette classe, donc la synchronisation n'est généralement pas nécessaire. Cependant, la classe est accessible à tout ce que l'intiailiser statique appelle (y compris en provoquant l'appel d'autres initialiseurs statiques).

Une classe peut être chargée par une classe chargée mais pas nécessairement initialisée immédiatement. Bien entendu, une classe peut être chargée par plusieurs instances de chargeurs de classe et ainsi devenir plusieurs classes avec le même nom.

Tom Hawtin - Tacle
la source
3

Oui, en quelque sorte

Un staticinitialiseur n'est appelé qu'une seule fois, donc selon cette définition, il est thread-safe - vous auriez besoin de deux ou plusieurs invocations de l' staticinitialiseur pour même obtenir une contention de thread.

Cela dit, les staticinitialiseurs sont déroutants à bien d'autres égards. Il n'y a vraiment aucun ordre spécifié dans lequel ils sont appelés. Cela devient vraiment déroutant si vous avez deux classes dont les staticinitialiseurs dépendent l'un de l'autre. Et si vous utilisez une classe mais n'utilisez pas ce que l' staticinitialiseur configurera, vous n'êtes pas assuré que le chargeur de classe appellera l'initialiseur statique.

Enfin, gardez à l'esprit les objets sur lesquels vous synchronisez. Je me rends compte que ce n'est pas vraiment ce que vous demandez, mais assurez-vous que votre question ne demande pas vraiment si vous devez rendre addController()thread-safe.

Mat
la source
5
Il existe un ordre très défini dans lequel ils sont appelés: Par ordre dans le code source.
mafu
De plus, ils sont toujours appelés, peu importe si vous utilisez leur résultat. Sauf si cela a été changé dans Java 6.
mafu
8
Dans une classe, les initialiseurs suivent le code. Étant donné deux classes ou plus, il n'est pas défini comme quelle classe est initialisée en premier, si une classe est initialisée à 100% avant qu'une autre commence, ou comment les choses sont "entrelacées". Par exemple, si deux classes ont chacune des initiateurs statiques se référant les uns aux autres, les choses se compliquent rapidement. Je pensais qu'il y avait des moyens de faire référence à un int final statique à une autre classe sans invoquer les initialiseurs, mais je ne vais pas en discuter d'une manière ou d'une autre
Matt
Ça devient moche et je l'éviterais. Mais il existe une manière définie de résoudre les cycles. Citant "Le langage de programmation Java 4e édition": Page: 75, Section: 2.5.3. Initialisation statique: "Si des cycles se produisent, les initialiseurs statiques de X n'auront été exécutés que jusqu'au point où la méthode de Y a été appelée. Lorsque Y, à son tour, appelle la méthode X, cette méthode s'exécute avec le reste des initialiseurs statiques encore à exécuter "
JMI MADISON
0

Oui, les initialiseurs statiques ne sont exécutés qu'une seule fois. Lisez ceci pour plus d'informations .

Mike Pone
la source
2
Non, ils peuvent être exécutés plus d'une fois.
Expiation limitée le
5
Non, ils ne peuvent être exécutés qu'une seule fois PAR CLASSLOADER.
ruurd
Réponse de base: l'initialisation statique ne s'exécute qu'une seule fois. Réponse avancée: L'initialisation statique s'exécute une fois par chargeur de classe. Le premier commentaire est déroutant car le phrasé mélange ces deux réponses.
JMI MADISON
-4

Donc, fondamentalement, puisque vous voulez une instance singleton, vous devez le faire plus ou moins à l'ancienne et vous assurer que votre objet singleton est initialisé une et une seule fois.

ruurd
la source