Действительно ли заключительные статические переменные ориентированы на многопотоковое исполнение в Java?

Я читал вокруг вполне немного, но не нашел категорический ответ.

У меня есть класс, который похож на это:

    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();

TIA.

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

26
задан pschang 9 March 2010 в 21:25
поделиться

8 ответов

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

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

.clone () в корне не работает, держитесь подальше

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

24
ответ дан 28 November 2019 в 07:02
поделиться

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

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

8
ответ дан 28 November 2019 в 07:02
поделиться

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

0
ответ дан 28 November 2019 в 07:02
поделиться

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

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

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

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

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

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

5
ответ дан 28 November 2019 в 07:02
поделиться

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

И да, это так.

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

1
ответ дан 28 November 2019 в 07:02
поделиться

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

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

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

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

if (!map.containsKey("foo")) {
    map.put("foo", bar);
}
3
ответ дан 28 November 2019 в 07:02
поделиться

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

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

6
ответ дан 28 November 2019 в 07:02
поделиться

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

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

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

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

5
ответ дан 28 November 2019 в 07:02
поделиться
Другие вопросы по тегам:

Похожие вопросы: