Вероятно, это помогает объяснить, почему такое правило вообще существует.
Java — это процедурный язык. т.е. вы говорите Java, как сделать что-то для вас. Если Java выполнит ваши инструкции не в том порядке, в котором вы написали, это, очевидно, не сработает. Например. в приведенном ниже примере, если Java сделает 2 -> 1 -> 3, то тушеное мясо будет испорчено.
1. Take lid off
2. Pour salt in
3. Cook for 3 hours
Итак, почему правило не говорит просто: «Java выполняет то, что вы написали, в том порядке, в котором вы написали»? В двух словах, потому что Java умна. Возьмем следующий пример:
1. Take eggs out of the freezer
2. Take lid off
3. Take milk out of the freezer
4. Pour egg and milk in
5. Cook for 3 hours
Если бы Java был таким же, как я, он просто выполнял бы его по порядку. Однако Java достаточно умен, чтобы понять, что он более эффективен И что конечный результат будет таким же, если он сделает 1 -> 3 -> 2 -> 4 -> 5 (вам не нужно снова идти в морозильник, и рецепт не меняется).
Таким образом, правило «Каждое действие в потоке происходит до каждого действия в этом потоке, которое происходит позже в порядке выполнения программы» означает: «В одном потоке ваша программа будет работать как если бы он был выполнен именно в том порядке, в котором вы его написали. Мы можем изменить порядок за кулисами, но мы гарантируем, что ничто из этого не изменит вывод.
Все идет нормально. Почему это не делает то же самое в нескольких потоках? В многопоточном программировании Java недостаточно умен, чтобы делать это автоматически. Это будет для некоторых операций (например, объединение потоков, запуск потоков, когда используется блокировка (монитор) и т. д.), но для других вещей вам нужно явно указать, чтобы он не выполнял переупорядочение, которое изменило бы вывод программы (например, volatile
маркер на полях , использование замков и т. д.).
Примечание:
Небольшое дополнение о том, что "происходит до отношений". Это причудливый способ сказать, что независимо от того, что может сделать переупорядочение Java, событие А произойдет раньше, чем событие Б. В нашем странном более позднем примере с тушеным мясом: «Шаг 1 и 3 происходит до шага 4». и молоко в "". Также, например, «Шаг 1 и 3 не нуждаются в отношении происходит до, потому что они никак не зависят друг от друга».
По дополнительному вопросу и ответу на комментарий
Во-первых, давайте установим, что означает «время» в мире программирования. В программировании у нас есть понятие «абсолютного времени» (сколько сейчас времени в мире?) и понятие «относительного времени» (сколько времени прошло с момента x?). В идеальном мире время есть время, но если у нас нет встроенных атомных часов, абсолютное время нужно время от времени корректировать. С другой стороны, для относительного времени нам не нужны поправки, поскольку нас интересуют только различия между событиями.
В Java System.currentTime()
имеет дело с абсолютным временем, а System.nanoTime()
— с относительным временем. Вот почему в Javadoc к nanoTime говорится: «Этот метод может использоваться только для измерения прошедшего времени и не связан ни с каким другим понятием системного или настенного времени».
На практике и currentTimeMillis, и nanoTime являются нативными вызовами, и поэтому компилятор не может практически доказать, что переупорядочивание не повлияет на правильность, то есть не изменит порядка выполнения.
Но давайте представим, что мы хотим написать реализацию компилятора, которая действительно просматривает нативный код и переупорядочивает все, если это допустимо. Когда мы смотрим на JLS, все, что он говорит нам, это то, что «вы можете изменить порядок всего, что угодно, пока это не может быть обнаружено». Теперь, как автор компилятора, мы должны решить, не нарушит ли переупорядочение семантику. Для относительного времени (nanoTime) было бы явно бесполезно (т. е. нарушает семантику), если бы мы изменили порядок выполнения. Теперь, не нарушит ли семантика, если мы переупорядочим абсолютное время (currentTimeMillis)? Пока мы можем ограничить разницу между источником мирового времени (скажем, системными часами) и тем, что мы решим (например, «50 мс»)*, я говорю «нет». Для приведенного ниже примера:
long tick = System.currentTimeMillis();
result = compute();
long tock = System.currentTimeMillis();
print(result + ":" + tick - tock);
Если компилятор сможет доказать, что compute()
требует меньшего, чем максимальное отклонение от системных часов, которое мы можем разрешить, тогда было бы законно переупорядочить это следующим образом:
long tick = System.currentTimeMillis();
long tock = System.currentTimeMillis();
result = compute();
print(result + ":" + tick - tock);
Поскольку это не нарушит спецификацию, которую мы определили, и, следовательно, не нарушит семантику.
Вы также спросили, почему это не включено в JLS. Я думаю, что ответ будет «чтобы JLS был коротким». Но я мало что знаю об этой области, поэтому вы можете задать отдельный вопрос об этом.
*: В реальных реализациях эта разница зависит от платформы.
person
Enno Shioji
schedule
27.03.2013
synchronized
из примера Мэнсона (не относящийся к порядку однопоточной программы), то, поскольку ни одна из операций не влияет ни на одну из других, их можно свободно переупорядочивать. - person Andrew Bissell   schedule 27.03.2013