Не уверен, что использование volatile имеет смысл, похоже, имеет ту же проблему - PullRequest
4 голосов
/ 08 декабря 2011

Чтение здесь:

JLS 8.3.1.4 volatile Fields

Без volatile он говорит

", тогда метод два мог быиногда выведите значение для j, которое больше, чем значение i, потому что пример не включает в себя синхронизацию и "

class Test {
    static volatile int i = 0, j = 0;
    static void one() { i++; j++; }
    static void two() {
        System.out.println("i=" + i + " j=" + j);
    }
}

С изменяемым значением говорит

"Возможно, однако, что любой данный вызов метода два может наблюдать значение для j, которое намного больше, чем значение, наблюдаемое для i, потому что метод один может выполняться много раз между моментом, когда методдва извлекает значение i и момент, когда метод два извлекает значение j. "

In ведет себя" правильно "с синхронизацией, но я не совсем понимаю, какая выгода volatile приносит сюда?

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

Это опечатка в документе?Если нет, объясните, как j может быть больше i при использовании volatile.

Ответы [ 6 ]

2 голосов
/ 08 декабря 2011

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

read i
run method 1
run method 1
read j
1 голос
/ 08 декабря 2011

Вы получили поведение летучего права;вы просто не прочитали внимательно то, что цитируете:

, потому что метод один может выполняться много раз между моментом, когда метод два извлекает значение i, и моментом, когда метод два извлекает значение j

Порядок сохраняется в one(), то есть в two(), i выбирается и печатается, но за время, необходимое для печати i и i, и j может быть многократно увеличен вызовами one() из других потоков, поэтому напечатанное значение для j будет выше напечатанного значения i.

1 голос
/ 08 декабря 2011

Из того, что я понимаю, volatile гарантирует, что разные потоки будут ссылаться на одну и ту же переменную вместо ее копирования, т. Е. Если вы обновите переменную volatile в потоке, у всех остальных будет это обновление переменной, поскольку все они ссылаются на одно и то же.Хороший пример этого можно найти по адресу Почему Volatile Matters .

Что касается метода два, это то, что он не атомарный.Он не будет работать только в одном цикле процессора.Вы можете разделить его на различные операции, как указано @Sign.Даже i ++ не является атомарным, поскольку ему нужно прочитать переменную i, увеличить ее и снова сохранить в памяти по ссылке i.

1 голос
/ 08 декабря 2011

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

Также еще два пункта:

  • i ++ - это не одна операция, а три: а) чтение переменной i, б) приращение, в) сохранение. Эта «тройная» операция не является атомарной, это означает, что нет гарантии, которая была бы выполнена без какого-либо другого потока, рассматривающего его несовместимое состояние. Если вы хотите это сделать, посмотрите на AtomicInteger # getAndIncrement ()
  • Ваш метод one () не синхронизирован, поэтому у вас может быть один поток, завершивший i ++, затем второй поток напечатает, а первый завершит операцию j ++.
0 голосов
/ 08 декабря 2011

В «порядке исходного кода» увеличение до i происходит до увеличение до j. Таким образом, метод вызова потока one() всегда будет наблюдать, что i >= j. Но когда другие потоки видят эти переменные, они могут видеть вещи по-другому.

Существуют определенные события, которые устанавливают то, что JLS называет «порядком синхронизации». Имеет смысл говорить об этих событиях (и только этих событиях) как о "происходящих раньше" других. Запись в переменную volatile является одним из них. Без использования volatile бессмысленно говорить, что i увеличивается до j; эти записи могут быть переупорядочены, и этот порядок может наблюдаться другими потоками.

Лучший пример того, что может произойти без volatile, будет следующим:

static void oneAndAHalf() { System.out.println("j=" + j + " i=" + i);

Даже если j появляется для увеличения после i и j выбирается до i, вы все равно можете наблюдать j > i, поскольку удаление volatile позволит переупорядочить операции в one(). Добавьте volatile, и oneAndAHalf() всегда будет отображать i >= j, как вы ожидаете.

Если вы уберете volatile, то метод two() может напечатать значение для j, которое больше i по одной из двух причин: поскольку операции были переупорядочены, или потому что i и j не рассматриваются атомарно. Текущий метод two() не однозначно иллюстрирует полезность volatile. Добавьте volatile, и вы получите тот же результат, но единственная причина в том, что операции не являются атомарными.

Чтобы увидеть согласованное представление, где i == j, оба метода могут быть synchronized. Это приведет к тому, что приращение к обеим переменным будет атомарным.

0 голосов
/ 08 декабря 2011

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

Я предлагаю вместо этого использовать AtomicInteger.

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