Применяется ли в 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 начинает новую тему), но не ноль.

Другие вопросы по тегам