Применяется ли в Java новая модель памяти также и к члену объекта, который объявлен как volatile?
В новой модели памяти Java любая запись в переменную гарантированно будет завершена до того, как следующий поток прочитает ее.
Интересно, это также относится и к переменным, которые являются членами этого объекта.
для модели памяти Java:
http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html
Например
public class VolatileTest {
private volatile Map<String, Function> functionMap = new HashMap<>();
public Function getFunction(String key) {
Function function = this.functionMap.get(key);
if (function == null) {
//Is this guaranteed to be fully constructed? I don't think so.
function = new Function(key);
this.functionMap.put(key, function);
}
return function;
}
}
Как и выше код, даже сделав functionMap
volatile, он все еще не гарантирует, что функциональный объект будет полностью создан до возврата этого метода.
Правильно ли мое мышление?
Также только для этой темы, я хотел бы, чтобы вы, ребята, проверили, правильно ли я считаю следующее:
Как ниже любой пишите functionMap
гарантированно будет завершено до изменения ссылки functionMap
, право? Независимо от того, как долго initializeMap
методы берут, другой поток либо увидит ноль functionMap
или полностью инициализированный functionMap
?
public Map<String,Function> getFunctionMap (){
Map<String, Function> tempMap = new HashMap<>();
initalizeMap(tempMap); //fill in values
// Above operation is guaranteed to be completed
// before changing the reference of this variable.
this.functionMap = tempMap;
// So here you will either see a null or a fully initialized map.
// Is my understanding right?
return this.functionMap;
}
Просто для пояснения выше, два приведенных выше примера все находятся в многопоточной среде, и переменные functionMap будут доступны многопоточному.
3 ответа
Именно тогда следует использовать ConcurrentHashMap
private final ConcurrentMap<String, Function> functionMap = new ConcurrentHashMap<>();
public Function getFunction(String key) {
Function function = functionMap.get(key);
if (function == null) {
function = new Function(key);
Function oldFunction = functionMap.putIfAbscent(function);
if (oldFunction != null) {
function = oldFunction;
}
}
return function;
}
@grumpynerd Привет, Ваше первое предположение о новой функции (ключ) может быть частично создано после возврата метода, за исключением случаев, когда это неизменный объект.
Ваше второе предположение неверно.
this.functionMap = tempMap;
// So here you will either see a null or a fully initialized map.
// Is my understanding right?
return this.functionMap;
теперь здесь вы пишете и читаете одну и ту же переменную переменную рядом со следующей. И вы предполагаете, что initializeMap будет завершен до оператора return. Ваше предположение дыра Правда в модели Roach Motel. Но это не обязательно верно в JMM. поскольку JMM более слабая модель, чем Роуч. См. Ответ на вопрос ниже. Является ли это лучшей версией двойной проверки блокировки без энергозависимости и синхронизации?
Обновленный отсюда, поскольку это не может прибыть в комментарии. Прошу прощения, ваше второе предположение также верно. Но имейте в виду this.functionMap = tempMap; на практике может вести себя так, как будто все операторы были выполнены до этого и были сброшены в память, как это объясняется в этой книге, написанной Дугой Леа http://g.oswego.edu/dl/jmm/cookbook.html, но это только руководство о том, как реализовать JMM. Например, посмотрите код здесь. Это лучшая версия двойной проверки блокировки без энергозависимости и синхронизации
здесь я пытался использовать летучий забор в качестве барьера памяти, даже если вы делаете заявление
fence = true;
fence = fence;
Теперь я делаю изменчивое чтение и запись, предполагая, что это сделает мой объект полностью инициализированным перед возвратом ссылки, и, следовательно, если я снова проверю эту ссылку на ненулевое, то я уверен, что объект будет полностью инициализирован. Но приведенные выше рассуждения ошибочны, потому что я пытаюсь мыслить с точки зрения барьеров памяти, а не гарантий Java-до. Недостатком моего вопроса было то, что ссылка на поле не была изменчивой, поэтому любой другой поток не считывает какую-либо изменяемую ссылку. Если его найденное поле!- null, даже тогда он может прочитать неправильные значения для других полей экземпляра FieldType. Поскольку нет никаких гарантий со стороны Java при чтении энергонезависимых значений.
Поэтому никогда не мыслит с точки зрения ограничения порядка утверждений. Всегда думайте, что гарантировано JLS/JMM, происходит до того, как отношения.
например
int count = 1; логический флаг volatile = true;
Теперь JLS заявляет, что приведенный выше код будет работать в программном порядке. то есть count будет сначала 1, а затем будет выполняться флаг = true.
But Not the catch above from jls http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html
It should be noted that the presence of a happens-before relationship
between two actions does not necessarily imply that they
have to take place in that order in an implementation.
If the reordering produces results consistent with a legal execution,
it is not illegal.
For example, the write of a default value to every
field of an object constructed by a thread need not
happen before the beginning of that thread, as long as no
read ever observes that fact.
Выше указано, что count = 1 может быть выполнено после flag = true. если исполнение является законным исполнением. Но наши наблюдения были бы такими, что count = 1 был выполнен первым. Ключевое слово - наблюдение. (Это может произойти и здесь, если предположить, что компилятор обнаружит, что флаг не является общим для других потоков, тогда это может быть допустимо... это всего лишь гипотеза)
но теперь, если в другом потоке выполняет приведенный ниже оператор, то
if(flag == true) {
sysout(count) // count is guaranteed to be 1
// because of volatile guarantees flag will be only true if it has been set by first thread
// and by program order count must have been set before flag has been set to true.
// therefore by program order i am printing count after reading value of a
//volatile variable flag, therefore count must be 1.
}
теперь, если флаг не был бы изменчивым, тогда между двумя потоками не было бы отношения "до и после". поток 2 очень хорошо мог прочитать флаг == true и считать как 0, так как нет никаких наблюдательных гарантий среди потоков.
Короче говоря, гарантии для наблюдений, а не как на самом деле выполняется каждое утверждение. Узнайте больше о Порядке программы, Порядке синхронизации и Происходит до заказа, и давайте поделимся своими знаниями:) http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html
Маркировка переменной volatile
полезно только в том случае, если значение переменной изменяется и доступно более чем одному потоку. Ваш functionMap
является ссылкой на карту, и если вы не хотите назначить новый экземпляр Map для этой ссылки, volatile здесь бесполезна. Для ясности, volatile не повлияет на содержимое карты, а только на указатель карты.
Я чувствую, что вы думаете, что вызовы методов Java являются асинхронными, это не так. В вашем первом примере new Function(key);
звонок не вернется, пока Function
Объект полностью инициализирован.
То же самое для второго, если только это initalizeMap
запускает новый поток, чтобы делать все это, здесь нет проблем. И даже в том случае, если intializeMap
делает некоторую асинхронную работу, как у вас есть new HashMap<>()
, tempMap не может быть нулевым, может быть не полностью загружен со значениями, добавленными initalizeMap (повторяю только если initalizeMap
начинает новую тему), но не ноль.