Являются ли конечные статические переменные потокобезопасными в 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, но хеш-карты, которые он содержит, также должны иметь тип ConcurrentHashMap? В основном..

private static final ConcurrentHashMap<String, HashMap> sharedData;

или

private static final ConcurrentHashMap<String, ConcurrentHashMap> sharedData;

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

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

ТИА.

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

8 ответов


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

только если вы и будет ли у вас полная безопасность потоков на карте. Любые содержащиеся карты должны быть неизменяемыми или одним из параллельные реализации также.

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

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


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

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


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

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


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

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

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

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

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

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


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

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

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

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


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

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

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

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

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

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

и да, это так.

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


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