У вас есть обычная память, ссылка «currentpos» и объект Point и его поля за ним, разделенные между двумя потоками без синхронизации. Таким образом, не существует определенного порядка между операциями записи, происходящими в этой памяти в основном потоке, и операциями чтения в созданном потоке (назовем его Т).
Основной поток выполняет следующие записи (игнорирование начальной настройки точки приведет к тому, что px и py будут иметь значения по умолчанию):
- to p.x
- to p.y
- to currentpos
Поскольку в этих операциях записи нет ничего особенного с точки зрения синхронизации/барьеров, среда выполнения может позволить потоку T видеть, как они происходят в любом порядке (основной поток, конечно, всегда видит операции записи и чтения, упорядоченные в соответствии с порядком программы), и происходит в любой момент между чтениями в T.
Итак, Т делает:
- читает currentpos в p
- читать p.x и p.y (в любом порядке)
- сравните и возьмите ветку
- прочитать px и py (в любом порядке) и вызвать System.out.println
Учитывая, что между операциями записи в main и чтениями в T нет упорядоченных отношений, очевидно, что есть несколько способов, которыми это может привести к вашему результату, поскольку T может увидеть запись main в currentpos до записи в currentpos.y или currentpos.x:
- Сначала он читает currentpos.x, до того, как произошла запись x — получает 0, затем читает currentpos.y до того, как произошла запись y — получает 0. Сравните evals с true. Записи становятся видимыми для T. Вызывается System.out.println.
- Он сначала читает currentpos.x, после того как произошла запись x, затем читает currentpos.y до того, как произошла запись y, и получает 0. Сравните evals с истинным. Записи становятся видимыми для T... и т.д.
- Сначала он считывает currentpos.y до того, как произойдет запись y (0), затем читает currentpos.x после записи x и возвращает значение true. и Т. Д.
и так далее... Здесь есть ряд гонок данных.
Я подозреваю, что ошибочное предположение здесь заключается в том, что записи, полученные в результате этой строки, становятся видимыми во всех потоках в порядке выполнения программы потока, выполняющего ее:
currentPos = new Point(currentPos.x+1, currentPos.y+1);
Java не дает такой гарантии (это было бы ужасно для производительности). Необходимо добавить что-то еще, если вашей программе требуется гарантированный порядок операций записи относительно операций чтения в других потоках. Другие предлагали сделать поля x,y окончательными или, в качестве альтернативы, сделать currentpos изменчивым.
- Если вы сделаете поля x,y окончательными, то Java гарантирует, что запись их значений будет замечена до возврата конструктора во всех потоках. Таким образом, поскольку присваивание currentpos выполняется после конструктора, поток T гарантированно увидит записи в правильном порядке.
- Если вы сделаете currentpos volatile, то Java гарантирует, что это точка синхронизации, которая будет полностью упорядочена по отношению к другим точкам синхронизации. Так как в main записи в x и y должны происходить до записи в currentpos, то любое чтение currentpos в другом потоке должно также видеть записи x, y, которые произошли раньше.
Использование final имеет то преимущество, что оно делает поля неизменяемыми и, таким образом, позволяет кэшировать значения. Использование volatile приводит к синхронизации при каждой записи и чтении currentpos, что может снизить производительность.
Подробности см. в главе 17 спецификации языка Java:http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html
(Первоначальный ответ предполагал более слабую модель памяти, так как я не был уверен, что гарантированной изменчивости JLS было достаточно. Ответ отредактирован, чтобы отразить комментарий от assylias, указывая на то, что модель Java сильнее - происходит - до транзитивности - и поэтому изменчивости на currentpos также достаточно. ).
person
paulj
schedule
15.08.2013
currentPos
неhappen-before
чтение, но я не понимаю, как это может быть проблемой. - person Dog   schedule 23.04.2013final
(который не влияет на создаваемый байт-код) в поляx
иy
решает ошибку. Хотя это не влияет на байт-код, поля помечаются им, что наводит меня на мысль, что это побочный эффект оптимизации JVM. - person Niv Steingarten   schedule 23.04.2013Point
p
, удовлетворяющийp.x+1 == p.y
, затем в поток опроса передается ссылка. В конце концов поток опроса решает выйти, потому что считает, что условие не выполняется для одного из полученных имPoint
, но затем вывод консоли показывает, что оно должно было быть выполнено. Отсутствиеvolatile
здесь просто означает, что поток опроса может застрять, но проблема явно не в этом. - person Erma K. Pizarro   schedule 23.04.2013p.x
иp.y
изменить нельзя, они устанавливаются только в конструктореPoint
. Единственный способ получитьPoint
— это вызвать конструктор, который не возвращает значение до тех пор, пока не будут установленыp.x
иp.y
. Ясно, что никакой код здесь не изменяет уже существующийPoint
. - person Dog   schedule 23.04.2013currentPos
volatile
. Это обеспечит запись в конструктореhappen-before
записьюcurrentPos
. Другой способ - просто сделать поля вPoint
final
, что будет работать из-за раздела семантики финального поля JLS. Синхронизированный блок - это просто noop, который OP поставил для воспроизведения проблемы. Даже не зная модели памяти Java, большинство людей предположило бы, что эта программа является потокобезопасной. - person L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o   schedule 29.06.2013synchronized
устраняет ошибку? Это потому, что мне приходилось писать код случайным образом, пока я не нашел такой, который детерминистически воспроизводил бы это поведение. - person Dog   schedule 15.08.2013