Чи безпечні нитки статичних ініціалізаторів 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) {
        // ...
    }
}

10
Мені не подобається ця конструкція, оскільки вона незаперечна. Погляньте на ін'єкцію залежності.
dfa

Відповіді:


199

Так, статичні ініціалізатори Java безпечні для потоків (скористайтеся першою опцією).

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


2
Однак клас може завантажуватися декількома завантажувачами класів, тому addController все ще може дзвонити не один раз (незалежно від того, синхронізуєте ви виклик чи ні) ...
Меттью Мердок

4
Так, тоді ми говоримо, що блок статичного коду насправді викликається для кожного завантажувача класів, який завантажує клас. Гм ... я думаю, це все ще повинно бути нормальним, однак, мені цікаво, як працює такий код коду в OSGI env, з
багатозахисними навантажувачами пакетних

1
Так. Блок статичного коду викликається для кожного завантажувача класів, який завантажує клас.
Меттью Мердок

3
@ simon622 Так, але він буде працювати в об'єкті іншого класу в кожному ClassLoader. Об'єкти різних класів, які все ще мають однакове повноцінне ім'я, але представляють різні типи, які не можна передавати один одному.
Ервін Болвідт

1
чи означає це, що ключове слово "остаточний" є зайвим у власнику екземпляра: 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 завантажується лише тоді, коли ви викликаєте екземпляр ()


18
Ви базуєте цю відповідь на тому, що статичний блок буде виконаний лише один раз у глобальному масштабі - саме це питання було задано.
Майкл Майерс

2
Я думаю, що це теж не безпечно в умовах багатокласного навантажувача.
Ахмад

2
@Ahmad Універсальні завантажувачі середовища розроблені таким чином, щоб кожен додаток мав власні одиночні кнопки.
Пітер Лорі

4

У звичайних обставинах відбувається все в статичному ініціалізаторі - перед усім, що використовує цей клас, тому синхронізація зазвичай не потрібна. Однак клас доступний для всього, до чого викликає статичний інтілайзер (включаючи викликання інших статичних ініціалізаторів).

Клас може бути завантажений класом, завантаженим, але не обов'язково ініціалізованим відразу. Звичайно, клас може бути завантажений декількома екземплярами завантажувачів класів і тим самим стати декількома класами з однаковою назвою.


3

Так, начебто

staticІніціалізатор тільки викликається один раз, так що за цим визначенням це потокобезпечна - вам потрібні два або більше викликів на staticініціалізатор навіть отримати нитка розбрат.

Однак, staticініціалізатори плутають багато інших способів. Дійсно не визначений порядок, у якому вони дзвоняться. Це стає дуже заплутаним, якщо у вас є два класи, staticініціалізатори яких залежать один від одного. І якщо ви використовуєте клас, але не використовуєте те, що staticбуде встановлено ініціалізатором, вам не гарантовано, що завантажувач класів буде викликати статичний ініціалізатор.

Нарешті, пам’ятайте про об’єкти, які ви синхронізуєте. Я усвідомлюю, що це насправді не те, що ви запитуєте, але переконайтеся, що ваше питання насправді не задається питанням, чи потрібно робити addController()безпеку для потоків.


5
Існує дуже визначений порядок, в якому вони викликаються: За порядком у вихідному коді.
мафу

Також їх завжди називають, незалежно від того, чи використовуєте ви їх результат. Якщо це не змінилося на Java 6.
mafu

8
У межах класу ініціалізатори слідують за кодом. Враховуючи два або більше класів, не так визначено, який клас ініціалізується першим, чи один клас ініціалізується на 100% до початку іншого, або як "переплітаються" речі. Наприклад, якщо два класи мають статичні інситалізатори, що посилаються один на одного, речі швидко стають некрасивими. Я думав, що існують способи, коли ви можете звернутися до статичного кінцевого int до іншого класу без виклику ініціалізаторів, але я не збираюся сперечатися так чи інакше
Метт

Це стає некрасивим, і я б цього уникав. Але існує визначений спосіб вирішення циклів. Цитування "Мова програмування Java 4-е видання": Сторінка: 75, Розділ: 2.5.3. Статична ініціалізація: "Якщо трапляються цикли, статичні ініціалізатори X виконуватимуться лише до тієї точки, де викликався метод Y. Коли Y, у свою чергу, викликає метод X, цей метод працює з рештою статичних ініціалізаторів, які ще мають бути виконані. "
JMI MADISON

0

Так, статичні ініціалізатори запускаються лише один раз. Прочитайте це для отримання додаткової інформації .


2
Ні, їх можна запускати не один раз.
Обмежене спокутування

5
Ні, їх можна запустити один раз на КЛАСУВАННЯ.
ruurd

Основна відповідь: Статичний ініт працює лише один раз. Розширена відповідь: Статичний init працює один раз на завантажувач класу. Перший коментар заплутаний, оскільки фразування змішує ці дві відповіді.
JMI MADISON

-4

Таким чином, оскільки ви хочете одиничний екземпляр, ви повинні зробити це більш-менш старомодним способом і переконайтеся, що ваш одиночний об'єкт ініціалізується один раз і лише один раз.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.