Java Concurrency: Volatile vs final в каскадных переменных?

is

final Map<Integer,Map<String,Integer>> status = new ConcurrentHashMap<Integer, Map<String,Integer>>();
Map<Integer,Map<String,Integer>> statusInner = new ConcurrentHashMap<Integer, Map<String,Integer>>();
status.put(key,statusInner);

такой же как

volatile Map<Integer,Map<String,Integer>> status = new ConcurrentHashMap<Integer,   Map<String,Integer>>();
Map<Integer,Map<String,Integer>> statusInner = new ConcurrentHashMap<Integer, Map<String,Integer>>();
status.put(key,statusInner);

в случае, если к внутренней карте обращаются разные потоки?

или даже что-то вроде этого требуется:

volatile Map<Integer,Map<String,Integer>> status = new ConcurrentHashMap<Integer, Map<String,Integer>>();
volatile Map<Integer,Map<String,Integer>> statusInner = new ConcurrentHashMap<Integer, Map<String,Integer>>();
status.put(key,statusInner);

В случае, если это НЕ «каскадная» карта, final и volatile в конечном итоге имеют тот же эффект, что и все потоки всегда видят правильное содержимое карты... Но что произойдет, если сама карта содержит карту, как в примере... Как мне убедиться, что внутренняя карта правильно "заблокирована памятью"?

Танки! Том


person Tom    schedule 03.06.2010    source источник
comment
final ConcurrentMap‹Integer,Map‹String,Integer›› status = new ConcurrentHashMap... рекомендуется   -  person Paul Whelan    schedule 03.06.2010


Ответы (3)


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

Если status равно final, конструкция содержащего класса создает отношение happens-before с любыми последующими чтениями, поэтому они могут видеть значение состояния. Точно так же любое чтение вашей переменной volatile гарантированно увидит последнее присваивание ей ссылки. Это не похоже на то, что вы очень часто меняете реальную карту, больше похоже на то, что вы просто меняете ключи, а общий объект карты остается как есть.

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

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

Это довольно странно сформулировано, но суть в том, что любая get операция, начало которой происходит после возврата некоторой put операции, гарантированно увидит результаты этого пута. Так что вам даже не нужно volatile на внешней карте; сказал JLS:

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

Резюме

Достаточно final на внешней карте.

person jasonmp85    schedule 03.06.2010
comment
Привет jasonmp85, спасибо за полезный ответ. Просто чтобы убедиться и перепроверить мое понимание: ваш пример с массивом int[] очень интересен и хорош. В случае, если бы я изменил только ArrayElements из разных потоков (а не сам экземпляр массива), я вынужден каким-то образом синхронизировать доступ к элементам массива, чтобы добиться того, что происходит до отношений. (Скорее всего, я бы синхронизировался по индексу, например, synchronized(index) { myintarray[index]=foo; } ? Надеюсь, это правильно!? Большое спасибо. - person Tom; 03.06.2010
comment
JLS является авторитетным источником по этому вопросу (java.sun.com /docs/books/jls/ Third_edition/html/memory.html), но да, вам придется принудительно выполнить какую-то синхронизацию между двумя потоками, совместно использующими элементы массива, иначе один поток может не видеть записи другого. Один из вариантов — переназначить ссылку на массив после любого обновления: volArray[5] = 10; volArray=volArray;. Хотя это нечетное самоназначение является изменчивой записью — синхронизируется с действием — поэтому любое чтение из этого массива в другом потоке после самоназначения гарантированно увидит новый элемент (происходит до того, как является транзитивным). - person jasonmp85; 03.06.2010
comment
Однако! Не делай этого. Он хрупкий, и кто-то изменит его, не понимая, что он ломает. Используйте что-то вроде этого класса (java. sun.com/javase/6/docs/api/java/util/concurrent/atomic/), если вам нужна изменчивая семантика для элементов массива. - person jasonmp85; 03.06.2010

Стоит обратить внимание на Google-Collections и, в частности, MapMaker, который позволяет разумно настраивать и создавать Карты. . Возможность настроить слабые значения, чтобы включить лучшую сборку мусора и время истечения срока действия, чтобы вы могли использовать Карты для эффективного кэширования, — это блестяще. Поскольку Карты, создаваемые MapMaker (:p), имеют те же свойства, что и ConcurrentHashMap, вы можете быть довольны его потокобезопасностью.

final mapMaker = new MapMaker().weakValues(); //for convenience, assign
final Map<Integer,Map<String,Integer>> status = mapMaker.makeMap();
status.put(key, mapMaker.<String, Integer>makeMap());

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

person Ben Smith    schedule 03.06.2010
comment
+1 за указание на MapMaker, которого я еще не видел. Мне всегда было интересно, где я могу найти параллельную слабую хеш-карту! - person jasonmp85; 03.06.2010

Я думаю, что лучший ответ здесь заключается в том, что volatile не является способом обеспечения потокобезопасности.

Использование ConcurrentHashMap — это почти все, что вам нужно. Да, сделайте ссылку на Map final верхнего уровня, если можете, но volatile ни в коем случае не нужно. Ссылка второго уровня Map внутри - это дело ConcurrentHashMap, чтобы сделать правильно, и предполагается, что это так.

person Sean Owen    schedule 03.06.2010
comment
Вы также можете захотеть показать, что карта второго уровня должна быть параллельной, если вы предоставляете карту верхнего уровня, а не инкапсулируете их (приведенные выше определения позволяют вставлять любую реализацию карты в верхний уровень; если вы предполагаете безопасность потоков на втором уровне вы должны указать тип карты, безопасный для потоков). - person Pete Kirkham; 03.06.2010
comment
Предположение о чем-то — довольно хороший способ ввести ошибку: предположение, что javadoc широко используемого класса JDK, суть которого заключается в предполагаемом поведении, унаследованном от довольно хорошо проверенного в боевых условиях пакета параллелизма Дуга Ли, кажется вам вероятным источником. ошибки? - person Sean Owen; 03.06.2010
comment
Я не видел никаких цитат, цитат или ссылок Javadoc. Если вы запомнили семантику ConcurrentHashMap, то конечно. Я не говорю, что ваши утверждения относительно ConcurrentHashMap неверны или что плакат не должен его использовать. Я говорю, что он/она должны проконсультироваться с Javadocs и убедиться, что его/ее предположения относительно семантики порядка чтения/записи ConcurrentHashMap верны, особенно если автор не знаком с классами. - person jasonmp85; 03.06.2010