Являются ли конечные статические переменные потокобезопасными в Java? - PullRequest
26 голосов
/ 09 марта 2010

Я много читал, но не нашел однозначного ответа.

У меня есть класс, который выглядит так:

    public class Foo() {

        private static final HashMap<String, HashMap> sharedData;

        private final HashMap myRefOfInnerHashMap;

        static {
           // time-consuming initialization of sharedData
           final HashMap<String, String> innerMap = new HashMap<String, String>;
           innerMap.put...
           innerMap.put...
           ...a

           sharedData.put(someKey, java.util.Collections.unmodifiableMap(innerMap));
        }

        public Foo(String key) {
            this.myRefOfInnerHashMap = sharedData.get(key);
        }

        public void doSomethingUseful() {
            // iterate over copy
            for (Map.Entry<String, String> entry : this.myRefOfInnerHashMap.entrySet()) {
                ...
            }
        }
     }

И мне интересно, является ли потокобезопасным доступ к sharedData из экземпляров Foo (как показано в конструкторе и в doSomethingUseful ()). Многие экземпляры Foo будут создаваться в многопоточной среде.

Я предполагаю, что sharedData инициализируется в статическом инициализаторе и не изменяется после этого (только для чтения).

Что я прочитал, так это то, что неизменяемые объекты по своей природе безопасны для потоков. Но я видел это только в контексте переменных экземпляра. Являются ли неизменяемые статические переменные потокобезопасными?

Другая конструкция, которую я нашел, была ConcurrentHashMap. Я мог бы сделать sharedData типа ConcurrentHashMap, но HashMaps, которые он содержит, также должны иметь тип ConcurrentHashMap? В основном ..

private static final ConcurrentHashMap<String, HashMap> sharedData;

или

private static final ConcurrentHashMap<String, ConcurrentHashMap> sharedData;

Или было бы безопаснее (но дороже просто клонировать ())?

this.myCopyOfData = sharedData.get(key).clone();

ТИА.

(Статический инициализатор был отредактирован, чтобы дать больше контекста.)

Ответы [ 8 ]

22 голосов
/ 09 марта 2010

ссылка до sharedData, которая является окончательной, является поточно-ориентированной, поскольку ее нельзя изменить. Содержимое карты NOT потокобезопасно, потому что его нужно либо обернуть предпочтительно реализацией Guava ImmutableMap, либо java.util.Collections.unmodifiableMap(), либо использовать одну из реализаций Map в пакете java.util.concurrent.

Только если вы сделаете ОБА , вы получите полную безопасность потоков на Карте. Любые содержащиеся в нем Карты должны быть неизменяемыми или также иметь одну из параллельных реализаций.

.clone () принципиально сломан, держитесь подальше

клонирование по умолчанию является мелким клоном, оно просто возвращает ссылки на объекты-контейнеры, а не полные копии. Это хорошо задокументировано в общедоступной информации о том, почему.

8 голосов
/ 09 марта 2010

Инициализация статических полей final в блоке статической инициализации является поточно-ориентированной. Однако помните, что объект, на который статические конечные контрольные точки могут не , является потокобезопасным. Если объект, на который вы ссылаетесь, является потокобезопасным (например, он неизменен), вы находитесь в открытом доступе.

Каждая отдельная HashMap, содержащаяся в вашей внешней HashMap, не гарантированно является поточно-ориентированной, если вы не используете ConcurrentHashMap, как предложено в вашем вопросе. Если вы не используете поточную безопасную внутреннюю реализацию HashMap, вы можете получить непредвиденные результаты, когда два потока обращаются к одному и тому же внутреннему HashMap. Имейте в виду, что только некоторые операции над ConcurrentHashMap синхронизируются. Например, итерация не является поточно-ориентированной.

6 голосов
/ 09 марта 2010

Что такое потокобезопасность? Несомненно, инициализация HashMap является поточно-ориентированной в том смысле, что все Foo совместно используют один и тот же экземпляр Map, и что карта гарантированно будет там, если не происходит исключение в статическом init.

Но изменение содержимого карты, безусловно, не безопасно для потоков. Статический финал означает, что карта общих данных не может быть переключена на другую карту. Но содержание карты - это другой вопрос. Если данный ключ используется более одного раза одновременно, у вас могут возникнуть проблемы с параллелизмом.

5 голосов
/ 09 марта 2010

Нет. За исключением случаев, когда они неизменны.

Единственное, что они делают, это

  • Быть доступным на уровне класса
  • Избегайте изменения ссылки.

Тем не менее, если ваш атрибут изменчив, он не является поточно-ориентированным.

См. Также: Синхронизируем ли мы переменные экземпляров, которые являются окончательными?

Это точно так же, за исключением того, что они на уровне класса.

5 голосов
/ 09 марта 2010

Да, это тоже потокобезопасно. Все последние члены вашего статического класса будут инициализированы до того, как какой-либо поток сможет получить к ним доступ.

Если во время инициализации произойдет сбой блока static, в потоке, который сначала попытается инициализировать, будет вызвано ExceptionInInitializerError. Последующая попытка сослаться на класс повысит NoClassDefFoundError.

Как правило, содержимое HashMap не гарантирует видимость между потоками. Однако код инициализации класса использует блок synchronized для предотвращения инициализации класса несколькими потоками. Эта синхронизация сбрасывает состояние карты (и экземпляров HashMap, которые она содержит), чтобы они были правильно видны всем потокам - при условии, что не внесены изменения в карту или в содержащиеся в ней карты вне класса инициализатор.

См. Спецификация языка Java, раздел 12.4.2 для получения информации об инициализации класса и требованиях к синхронизации.

3 голосов
/ 09 марта 2010

В переменной final static нет ничего поточно-ориентированного. Объявление переменной-члена final static гарантирует, что эта переменная назначается только один раз.

Вопрос о безопасности потоков связан не столько с тем, как вы объявляете переменные, а с тем, как он взаимодействует с переменными. Таким образом, на самом деле невозможно ответить на ваш вопрос без более подробной информации о вашей программе:

  • Изменяют ли несколько потоков состояние вашей переменной sharedData?
  • Если это так, вы синхронизируете все записи ( и чтения) sharedData?

Использование ConcurrentHashMap гарантирует только то, что отдельные методы Map являются поточно-ориентированными, но не делает такую ​​операцию безопасной, как эта:

if (!map.containsKey("foo")) {
    map.put("foo", bar);
}
1 голос
/ 09 марта 2010

Разве вы не спрашиваете, является ли статическая инициализация sharedData поточно-безопасной и выполняется только один раз?

И да, это так.

Конечно, многие люди здесь правильно отметили, что содержание sharedData все еще можно изменить.

0 голосов
/ 09 марта 2010

В этом случае неизменным является только объект sharedData, что означает только то, что вы все время будете работать с одним и тем же объектом. Но любые данные внутри него могут быть изменены (удалены, добавлены и т. Д.) В любое время из любого потока.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...