Czy statyczne inicjatory Java są bezpieczne dla wątków?

136

Używam bloku kodu statycznego do zainicjowania niektórych kontrolerów w rejestrze, który mam. Moje pytanie brzmi zatem, czy mogę zagwarantować, że ten statyczny blok kodu zostanie wywołany tylko raz, gdy klasa zostanie po raz pierwszy załadowana? Rozumiem, że nie mogę zagwarantować, kiedy ten blok kodu zostanie wywołany, zgaduję, że kiedy Classloader po raz pierwszy go załaduje. Zdaję sobie sprawę, że mogę zsynchronizować się z klasą w bloku kodu statycznego, ale przypuszczam, że tak właśnie się dzieje?

Prosty przykład kodu byłby;

class FooRegistry {

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

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

czy powinienem to zrobić;

class FooRegistry {

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

    private static void addController(IFooController controller) {
        // ...
    }
}
simon622
źródło
10
Nie podoba mi się ten projekt, ponieważ jest niesprawny. Spójrz na Dependency Injection.
dfa

Odpowiedzi:

199

Tak, statyczne inicjatory Java są bezpieczne dla wątków (użyj pierwszej opcji).

Jeśli jednak chcesz mieć pewność, że kod zostanie wykonany dokładnie raz, musisz upewnić się, że klasa jest ładowana tylko przez jeden program ładujący klasy. Inicjalizacja statyczna jest wykonywana raz na program ładujący klasy.

Matthew Murdoch
źródło
2
Jednak klasa może być ładowana przez wiele ładujących klas, więc addController może nadal być wywoływana więcej niż raz (niezależnie od tego, czy synchronizujesz wywołanie) ...
Matthew Murdoch
4
Aha, zaczekaj, więc mówimy, że statyczny blok kodu jest faktycznie wywoływany dla każdego programu ładującego klasę, który ładuje klasę. Hmm ... Myślę, że to nadal powinno być w porządku, jednak zastanawiam się, jak działałoby uruchamianie tego rodzaju kodu w środowisku OSGI, z
wieloma pakietami ładującymi
1
Tak. Statyczny blok kodu jest wywoływany dla każdego programu ładującego klasę, który ładuje klasę.
Matthew Murdoch
3
@ simon622 Tak, ale działałoby to w innym obiekcie klasy w każdym ClassLoader. Obiekty różnych klas, które nadal mają tę samą w pełni kwalifikowaną nazwę, ale reprezentują różne typy, których nie można rzutować na siebie.
Erwin Bolwidt,
1
czy to oznacza, że ​​słowo kluczowe „final” jest zbędne w posiadaczu instancji w: en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom ?
spc16670
11

To sztuczka, której możesz użyć do leniwej inicjalizacji

enum Singleton {
    INSTANCE;
}

lub dla wersji wcześniejszej niż Java 5.0

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

Ponieważ blok statyczny w SingletonHolder zostanie uruchomiony raz w sposób bezpieczny dla wątków, nie potrzebujesz żadnego innego blokowania. Klasa SingletonHolder zostanie załadowana tylko wtedy, gdy wywołasz instancję ()

Peter Lawrey
źródło
18
Opierasz tę odpowiedź na fakcie, że blok statyczny zostanie wykonany globalnie tylko raz - i to jest właśnie zadane pytanie.
Michael Myers
2
Myślę, że to też nie jest bezpieczne w środowisku ładowarek wielu klas, co powiedzieć.?
Ahmad,
2
@Ahmad Wieloklasowe środowiska ładujące są zaprojektowane tak, aby umożliwić każdej aplikacji posiadanie własnych singletonów.
Peter Lawrey
4

W zwykłych okolicznościach wszystko w statycznym inicjatorze dzieje się przed wszystkim, co używa tej klasy, więc synchronizacja zwykle nie jest konieczna. Jednak klasa jest dostępna dla wszystkiego, co wywołuje statyczny inicjator (w tym powoduje wywoływanie innych statycznych inicjatorów).

Klasa może być ładowana przez klasę załadowaną, ale niekoniecznie jest inicjowana od razu. Oczywiście klasa może być ładowana przez wielokrotne instancje programów ładujących klasy i tym samym stać się wieloma klasami o tej samej nazwie.

Tom Hawtin - haczyk
źródło
3

Tak, w pewnym sensie

staticInicjująca jest wywoływana tylko raz, więc według tej definicji jest to bezpieczne dla wątków - że potrzebne są dwa lub więcej wywołań z staticinicjatora nawet dostać wątku rywalizacji.

To powiedziawszy, staticinicjatory są mylące na wiele innych sposobów. Naprawdę nie ma określonej kolejności, w jakiej są wywoływane. Staje się to naprawdę zagmatwane, jeśli masz dwie klasy, których staticinicjatory są od siebie zależne. A jeśli używasz klasy, ale nie używasz tego, co staticskonfiguruje inicjator, nie masz gwarancji, że program ładujący klasy wywoła inicjator statyczny.

Na koniec pamiętaj o obiektach, na których się synchronizujesz. Zdaję sobie sprawę, że tak naprawdę nie o to pytasz, ale upewnij się, że twoje pytanie tak naprawdę nie dotyczy tego, czy musisz zabezpieczyć addController()wątki.

Matt
źródło
5
Istnieje bardzo określona kolejność ich wywoływania: Według kolejności w kodzie źródłowym.
mafu
Ponadto są zawsze wywoływane, bez względu na to, czy używasz ich wyniku. Chyba że zostało to zmienione w Javie 6.
mafu
8
W ramach klasy inicjatory podążają za kodem. Biorąc pod uwagę dwie lub więcej klas, nie jest to tak zdefiniowane, jak to, która klasa zostanie zainicjowana jako pierwsza, czy jedna klasa zostanie zainicjowana w 100% przed rozpoczęciem innej, ani jak elementy są „przeplatane”. Np. Jeśli dwie klasy mają statyczne inicjalizatory, które odnoszą się do siebie nawzajem, sprawy szybko się komplikują. Pomyślałem, że są sposoby, aby odnieść się do statycznego końcowego int do innej klasy bez wywoływania inicjatorów, ale nie zamierzam dyskutować w ten czy inny sposób
Matt
Robi się brzydko i unikałbym tego. Ale istnieje określony sposób rozwiązywania cykli. Cytując „Język programowania Java, wydanie czwarte”: Strona: 75, Rozdział: 2.5.3. Inicjalizacja statyczna: „Jeśli wystąpią cykle, statyczne inicjatory X zostaną wykonane tylko do momentu wywołania metody Y. Gdy Y z kolei wywołuje metodę X, ta metoda jest uruchamiana z pozostałymi statycznymi inicjatorami, które jeszcze nie zostały wykonane "
JMI MADISON
0

Tak, inicjatory statyczne są uruchamiane tylko raz. Przeczytaj to, aby uzyskać więcej informacji .

Mike Pone
źródło
2
Nie, można je uruchomić więcej niż raz.
Limited Atonement,
5
Nie można ich uruchomić raz NA CLASSLOADER.
ruurd
Podstawowa odpowiedź: statyczny init działa tylko raz. Odpowiedź zaawansowana: statyczny init jest uruchamiany raz na program ładujący klasy. Pierwszy komentarz jest mylący, ponieważ sformułowanie łączy te dwie odpowiedzi.
JMI MADISON,
-4

Zasadniczo, ponieważ potrzebujesz pojedynczej instancji, powinieneś zrobić to mniej więcej w staroświecki sposób i upewnić się, że obiekt singletona został zainicjowany raz i tylko raz.

ruurd
źródło