Являются ли 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) {
        // ...
    }
}
simon622
источник
10
Мне не нравится этот дизайн, так как он не тестируется. Посмотрите на инъекцию зависимостей.
DFA

Ответы:

199

Да, статические инициализаторы Java являются потокобезопасными (используйте ваш первый вариант).

Однако, если вы хотите убедиться, что код выполняется точно один раз, вам нужно убедиться, что класс загружается только одним загрузчиком классов. Статическая инициализация выполняется один раз для каждого загрузчика классов.

Мэтью Мердок
источник
2
Однако класс может быть загружен несколькими загрузчиками классов, поэтому addController может вызываться несколько раз (независимо от того, синхронизировали ли вы вызов или нет) ...
Мэтью Мердок
4
Ах, подожди, мы говорим, что статический блок кода вызывается для каждого загрузчика классов, который загружает класс. Хм ... Полагаю, что все должно быть в порядке, однако мне интересно, как будет работать такой код в среде OSGI с множественными загрузчиками классов.
simon622
1
Да. Статический блок кода вызывается для каждого загрузчика классов, который загружает класс.
Мэтью Мердок
3
@ simon622 Да, но он будет работать в разных объектах класса в каждом ClassLoader. Разные объекты Class, которые все еще имеют одно и то же полное имя, но представляют разные типы, которые нельзя привести друг к другу.
Эрвин Болвидт
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 будет запускаться один раз в поточно-ориентированном режиме, вам не нужны никакие другие блокировки. Класс SingletonHolder будет загружен только при вызове instance ()

Питер Лори
источник
18
Вы основываете этот ответ на том факте, что статический блок будет выполняться только один раз в глобальном масштабе - это тот самый вопрос, который был задан.
Майкл Майерс
2
Я думаю, что это тоже небезопасно в среде мультиклассовых загрузчиков.
Ахмад
2
@Ahmad Мультиклассовые загрузчики предназначены для того, чтобы каждое приложение имело свои собственные синглтоны.
Питер Лори
4

В обычных обстоятельствах все в статическом инициализаторе происходит - до всего, что использует этот класс, поэтому синхронизация обычно не требуется. Тем не менее, класс доступен для всего, что вызывает static intiailiser (в том числе вызывает другие статические инициализаторы).

Класс может быть загружен загруженным классом, но не обязательно инициализируется сразу. Конечно, класс может быть загружен несколькими экземплярами загрузчиков классов и таким образом стать несколькими классами с одинаковыми именами.

Том Хотин - Tackline
источник
3

Да вроде

staticИнициализатор только вызывается один раз, так что по этому определению это поточно - вам нужны два или более вызовыstatic инициализаторе даже получить нить раздор.

Тем не менее, staticинициализаторы сбивают с толку во многих других отношениях. Там действительно нет определенного порядка, в котором они называются. Это действительно сбивает с толку, если у вас есть два класса, staticинициализаторы которых зависят друг от друга. И если вы используете класс, но не используете то, что staticинициализатор настроит, вы не гарантированы, что загрузчик классов вызовет статический инициализатор.

Наконец, имейте в виду объекты, с которыми вы синхронизируете. Я понимаю, что на самом деле это не то, что вы спрашиваете, но убедитесь, что ваш вопрос на самом деле не спрашивает, нужно ли вам сделать addController()потокобезопасным.

Matt
источник
5
Существует очень определенный порядок, в котором они называются: По порядку в исходном коде.
Мафу
Кроме того, они всегда называются, независимо от того, используете ли вы их результат. Если это не было изменено в Java 6.
Mafu
8
Внутри класса инициализаторы следуют коду. Учитывая два или более классов, не так определено, какой класс инициализируется первым, является ли один класс инициализированным на 100% перед запуском другого, или как вещи «чередуются». Например, если два класса имеют статические инициализаторы, ссылающиеся друг на друга, все становится ужасно быстро. Я думал, что есть способы, которыми вы можете ссылаться на static final int на другой класс без вызова инициализаторов, но я не собираюсь спорить так или иначе
Мэтт
Это становится уродливым, и я бы избежал этого. Но есть определенный способ разрешения циклов. Цитата "Язык программирования Java, 4-е издание": страница 75, раздел 2.5.3. Статическая инициализация: «Если циклы случаются, статические инициализаторы X будут выполняться только до точки, где был вызван метод Y. Когда Y, в свою очередь, вызывает метод X, этот метод выполняется с остальными статическими инициализаторами, которые еще предстоит выполнить. "
JMI MADISON
0

Да, статические инициализаторы запускаются только один раз. Прочитайте это для получения дополнительной информации .

Майк Пон
источник
2
Нет, их можно запускать более одного раза.
Ограниченное искупление
5
Нет, они могут быть запущены один раз за CLASSLOADER.
ruurd
Основной ответ: Статическая инициализация запускается только один раз. Расширенный ответ: Статическая инициализация запускается один раз для загрузчика классов. Первый комментарий сбивает с толку, потому что формулировка смешивает эти два ответа.
JMI MADISON
-4

Таким образом, в основном, так как вам нужен экземпляр singleton, вы должны сделать это более или менее старомодным способом и убедиться, что ваш объект singleton инициализируется один раз и только один раз.

Руурд
источник