Подтвердить что ты не робот

Являются ли конечные статические переменные потоками безопасными в 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();

ТИА.

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

4b9b3361

Ответ 1

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

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

.clone() в корне нарушена, избегайте

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

Ответ 2

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

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

Ответ 3

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

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

Ответ 4

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

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

  • Доступный уровень класса
  • Избегайте ссылки, которую нужно изменить.

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

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

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

Ответ 5

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

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

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

Обратитесь к Language Language Specification, & sect; 12.4.2 для получения информации о инициализации класса и необходимости синхронизации.

Ответ 6

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

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

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

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

if (!map.containsKey("foo")) {
    map.put("foo", bar);
}

Ответ 7

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

И да, это так.

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

Ответ 8

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