Будет ли гарантирован поток, обрабатывающий последующий сетевой запрос, видеть значение изменчивой переменной, записанной во время предыдущего запроса? - PullRequest
2 голосов
/ 28 февраля 2020

У меня есть теоретический вопрос о модели памяти Java. Предположим, у меня есть сервер с этими двумя обработчиками запросов в следующем классе:

class MyHandlers {
  volatile int someFlag = 0;

  String handleFirstRequest() {
    someFlag = 1;
    return "Hello!";
  }

  String handleSecondRequest() {
    return "value of someFlag: " + someFlag;
  }
}

У меня также есть клиент. Мой клиент отправляет сетевой запрос, который запускает выполнение handleFirstRequest. Клиент ждет, пока запрос не завершится. Как только первый запрос завершается, клиент отправляет второй запрос, который вызывает handleSecondRequest.

Вопрос: Как модель памяти Java предотвращает ответ "value of someFlag: 0" на второй запрос ?

Примечания: я понимаю, что на практике поток, обрабатывающий второй ответ, всегда будет видеть someFlag как 1.

Если я правильно прочитал JMM, существует порядок синхронизации, который это общий порядок, который в моем примере упорядочил бы энергозависимое чтение и энергозависимую запись (someFlag = 1). Если чтение следует за записью, тогда чтение увидит запись. Возможно ли иметь случай, когда запись следует за чтением? В этом случае запись не синхронизируется с чтением, и между записью и чтением не было бы никакой связи. Это приведет к тому, что поток, обрабатывающий второй запрос, увидит someFlag как 0. Где мое понимание go неверно?

Дополнительные мысли (2020 Mar 2): JMM не обратитесь к понятию времени. Действия синхронизации упорядочены в соответствии с порядком синхронизации, но в JMM ничего не говорит о том, что порядок синхронизации такой же, как порядок действий, отсортированный по времени. Это предполагает, что реализация Java может упорядочить чтение someFlag до записи, даже если чтение произошло после записи в соответствии с часами. Похоже, что JMM гарантирует, что , если упорядоченное чтение упорядочено после энергозависимой записи, затем записи до того, как энергозависимая запись станет видимой для чтения после энергозависимого чтения.

Ответы [ 4 ]

2 голосов
/ 28 февраля 2020

Если первый запрос завершается, это означает, что someFlag = 1 был выполнен некоторым потоком. На этом этапе значение someFlag гарантированно будет видимым для любых других потоков, выполняющих чтение. Поэтому, когда приходит второй запрос, вы можете быть уверены, что он увидит значение 1.

1 голос
/ 17 марта 2020

Я нашел свой ответ!

В разделе 17.4.3 Java Спецификация языка говорится следующее:

Последовательная согласованность очень сильная гарантия, которая сделана о видимости и порядке выполнения программы. В последовательном последовательном выполнении существует общий порядок для всех отдельных действий (таких как чтение и запись), который согласуется с порядком программы, и каждое отдельное действие имеет атомы c и , немедленно видимые для каждого thread .

В модели памяти Java «действия» относятся к действиям между потоками, которые включают в себя энергозависимые записи. Этот параграф гарантирует, что любая реализация Java, которая соответствует JLS, гарантирует, что энергозависимые записи будут немедленно видны другим потокам. В примере из вступительной статьи этот параграф гарантирует, что энергозависимое чтение не может быть упорядочено до энергозависимой записи.

1 голос
/ 03 марта 2020

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

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

Таким образом, когда первый поток обновляет someFlag до 1, он становится видимым для всех потоков мгновенно (за счет меньшая производительность из-за предотвращения кеширования). Затем, когда второй поток читает его, он увидит значение, данное первым потоком.

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

Суммируя все это, второй запрос не сможет увидеть someFlag как 0.

0 голосов
/ 28 февраля 2020

В вашем описании вы отправляете запрос 2 после того, как запрос 1 завершен, поэтому запрос 2 определенно получен someFlag = 1


В другой ситуации: поток A только что запустил handleFirstRequest () еще не завершился, когда поток B пришел для чтения someFlag , B будет получено someFlag = 0

Да, это может быть ,, но с очень очень малой возможностью ...

В JMM есть 8 атомов c операций, которые вы, возможно, прочитали:

  • блокировка \ разблокировка
  • чтение \ запись
  • загрузка \ хранение
  • использование \ назначение

Да, каждая операция JMM имеет функцию атомарности, но они не могут быть одним атомом c в пределах заказанных исполнений JMM. Если поток хочет изменить someFlag, он будет читать - загружать - их ... и много операций jmm , все отдельные операции не имеют атомарности。


А что делает volatile? он использует CAS для предотвращения многопоточного конфликта и ennable Bus может воспринимать любую операцию записи JMM, а затем делать недействительными поля, записанные во всех потоках, которые прочитаны. Когда загруженное поле недопустимо, поток обновит его немедленно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...