Sind statische Java-Initialisierer threadsicher?

136

Ich verwende einen statischen Codeblock, um einige Controller in einer Registrierung zu initialisieren, die ich habe. Meine Frage ist daher, kann ich garantieren, dass dieser statische Codeblock beim ersten Laden der Klasse nur einmal unbedingt aufgerufen wird? Ich verstehe, dass ich nicht garantieren kann, wann dieser Codeblock aufgerufen wird. Ich schätze, es ist, wenn der Classloader ihn zum ersten Mal lädt. Mir ist klar, dass ich die Klasse im statischen Codeblock synchronisieren kann, aber ich vermute, dass dies tatsächlich sowieso passiert?

Ein einfaches Codebeispiel wäre;

class FooRegistry {

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

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

oder sollte ich das tun;

class FooRegistry {

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

    private static void addController(IFooController controller) {
        // ...
    }
}
simon622
quelle
10
Ich mag dieses Design nicht, da es nicht testbar ist. Schauen Sie sich Dependency Injection an.
dfa

Antworten:

199

Ja, statische Java-Initialisierer sind threadsicher (verwenden Sie Ihre erste Option).

Wenn Sie jedoch sicherstellen möchten, dass der Code genau einmal ausgeführt wird, müssen Sie sicherstellen, dass die Klasse nur von einem einzelnen Klassenladeprogramm geladen wird. Die statische Initialisierung wird einmal pro Klassenladeprogramm durchgeführt.

Matthew Murdoch
quelle
2
Eine Klasse kann jedoch von mehreren Klassenladeprogrammen geladen werden, sodass addController möglicherweise immer noch mehrmals aufgerufen wird (unabhängig davon, ob Sie den Aufruf synchronisieren oder nicht) ...
Matthew Murdoch,
4
Ah, Moment mal, wir sagen also, dass der statische Codeblock tatsächlich für jeden Klassenladeprogramm aufgerufen wird, der die Klasse lädt. Hmm ... Ich denke, das sollte immer noch in Ordnung sein, aber ich frage mich, wie das Ausführen dieser Art von Code in einer OSGI-Umgebung mit mehreren Bundle-Klassenladeprogrammen funktionieren würde.
simon622
1
Ja. Der statische Codeblock wird für jeden Klassenlader aufgerufen, der die Klasse lädt.
Matthew Murdoch
3
@ simon622 Ja, aber es würde in jedem ClassLoader in einem anderen Klassenobjekt arbeiten. Verschiedene Klassenobjekte, die immer noch denselben vollständig qualifizierten Namen haben, aber unterschiedliche Typen darstellen, die nicht ineinander umgewandelt werden können.
Erwin Bolwidt
1
Bedeutet dies, dass das Schlüsselwort 'final' im Instanzinhaber in: en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom redundant ist ?
spc16670
11

Dies ist ein Trick, den Sie für die verzögerte Initialisierung verwenden können

enum Singleton {
    INSTANCE;
}

oder für vor Java 5.0

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

Da der statische Block in SingletonHolder einmal threadsicher ausgeführt wird, benötigen Sie keine weitere Sperre. Die Klasse SingletonHolder wird nur geladen, wenn Sie instance () aufrufen.

Peter Lawrey
quelle
18
Sie stützen diese Antwort auf die Tatsache, dass der statische Block nur einmal global ausgeführt wird - genau diese Frage wurde gestellt.
Michael Myers
2
Ich denke, dass dies auch in einer Laderumgebung mit mehreren Klassen nicht sicher ist.
Ahmad
2
@Ahmad Multi-Class-Loader-Umgebungen ermöglichen es jeder Anwendung, ihre eigenen Singletons zu haben.
Peter Lawrey
4

Unter normalen Umständen geschieht alles im statischen Initialisierer - vor allem, was diese Klasse verwendet, sodass normalerweise keine Synchronisierung erforderlich ist. Auf die Klasse kann jedoch auf alles zugegriffen werden, was der statische Intiailiser aufruft (einschließlich des Aufrufs anderer statischer Initialisierer).

Eine Klasse kann von einer geladenen Klasse geladen, aber nicht unbedingt sofort initialisiert werden. Natürlich kann eine Klasse durch mehrere Instanzen von Klassenladeprogrammen geladen werden und dadurch zu mehreren Klassen mit demselben Namen werden.

Tom Hawtin - Tackline
quelle
3

Mehr oder weniger

Ein staticInitialisierer wird nur einmal aufgerufen, daher ist er nach dieser Definition threadsicher - Sie benötigen zwei oder mehr Aufrufe desstatic Initialisierers, um überhaupt Thread-Konflikte zu erhalten.

Das heißt, staticinitializers sind in vielerlei anderer Hinsicht verwirrend. Es gibt wirklich keine festgelegte Reihenfolge, in der sie aufgerufen werden. Dies wird sehr verwirrend, wenn Sie zwei Klassen haben, deren staticInitialisierer voneinander abhängen. Wenn Sie eine Klasse verwenden, aber nicht das verwenden, was der staticInitialisierer einrichten wird, können Sie nicht garantieren, dass der Klassenlader den statischen Initialisierer aufruft.

Denken Sie schließlich an die Objekte, auf denen Sie synchronisieren. Mir ist klar, dass dies nicht wirklich das ist, was Sie fragen, aber stellen Sie sicher, dass Ihre Frage nicht wirklich fragt, ob Sie addController()threadsicher machen müssen .

Matt
quelle
5
Es gibt eine sehr definierte Reihenfolge, in der sie aufgerufen werden: Nach Reihenfolge im Quellcode.
Mafu
Außerdem werden sie immer aufgerufen, egal ob Sie ihr Ergebnis verwenden. Es sei denn, dies wurde in Java 6 geändert.
Mafu
8
Innerhalb einer Klasse folgen Initialisierer dem Code. Bei zwei oder mehr Klassen ist nicht so definiert, welche Klasse zuerst initialisiert wird, ob eine Klasse zu 100% initialisiert wird, bevor eine andere startet, oder wie Dinge "verschachtelt" werden. Wenn beispielsweise zwei Klassen statische Initialisierer haben, die sich aufeinander beziehen, werden die Dinge schnell hässlich. Ich dachte, es gibt Möglichkeiten, wie Sie ein statisches final int auf eine andere Klasse verweisen könnten, ohne die Initialisierer aufzurufen, aber ich werde den Punkt nicht auf die eine oder andere Weise diskutieren
Matt
Es wird hässlich und ich würde es vermeiden. Es gibt jedoch einen definierten Weg, wie Zyklen aufgelöst werden. Zitat "The Java Programming Language 4th Edition": Seite: 75, Abschnitt: 2.5.3. Statische Initialisierung: "Wenn Zyklen auftreten, wurden die statischen Initialisierer von X nur bis zu dem Punkt ausgeführt, an dem die Methode von Y aufgerufen wurde. Wenn Y wiederum die X-Methode aufruft, wird diese Methode mit den übrigen noch auszuführenden statischen Initialisierern ausgeführt "
JMI MADISON
0

Ja, statische Initialisierer werden nur einmal ausgeführt. Lesen Sie dies für weitere Informationen .

Mike Pone
quelle
2
Nein, sie können mehrmals ausgeführt werden.
Begrenzte Versöhnung
5
Nein, sie können einmal pro Klassenlader ausgeführt werden.
Ruurd
Grundlegende Antwort: Static init wird nur einmal ausgeführt. Erweiterte Antwort: Static init wird einmal pro Klassenlader ausgeführt. Der erste Kommentar ist verwirrend, da die Formulierung diese beiden Antworten mischt.
JMI MADISON
-4

Da Sie also eine Singleton-Instanz möchten, sollten Sie dies mehr oder weniger auf die altmodische Weise tun und sicherstellen, dass Ihr Singleton-Objekt einmal und nur einmal initialisiert wird.

ruurd
quelle