Java происходит до и синхронизация

У меня небольшие разногласия по поводу происходит до в Java и синхронизации.

Представьте себе следующий сценарий:

Основная тема

MyObject o = new MyObject();    // (0)
synchronized (sharedMonitor) {
    // (1) add the object to a shared collection
}
// (2) spawn other threads

Другие темы

MyObject o;
synchronized (sharedMonitor) {
    // (3) retrieve the previously added object
}
// (4) actions to modify the object

Обратите внимание, что переменные экземпляра MyObject не являются ни volatile, ни final. Методы MyObject не используют синхронизацию.

Насколько я понимаю, это:

  • 1 происходит до 3, так как синхронизация происходит на том же мониторе, а другие потоки создаются только на 2 >, который выполняется после 1.

  • Действия на 4 не гарантируют того, что позже они будут видны основному потоку, если не будет дальнейшей синхронизации для всех потоков, и основной поток каким-то образом синхронизируется после этих действий.

Вопрос. Есть ли какие-либо гарантии того, что действия с номером 0 будут видны, что они будут выполняться до, а также будут иметь одновременный доступ на 3, или я должен объявить переменные как volatile?


Рассмотрим теперь следующий сценарий:

Основная тема

MyObject o = new MyObject();    // (0)
synchronized (sharedMonitor) {
    // (1) add the object to a shared collection
}
// (2) spawn other threads, and wait for their termination
// (5) access the data stored in my object.

Другие темы

MyObject o;
synchronized (sharedMonitor) {
    // (3) retrieve the previously added object
}
o.lock();    // using ReentrantLock
try {
    // (4) actions to modify the object
} finally { o.unlock(); }

Насколько я понимаю, это:

  • 1 происходит до 3, как и прежде.

  • Действия в 4 видны между другими потоками из-за синхронизации в ReentrantLock, удерживаемой MyObject.

  • Действия над 4 логически происходят после 3, но нет связи происходит до от 3 до 4. вследствие синхронизации на другом мониторе.

  • Пункт выше останется верным, даже если была синхронизация sharedMonitor после unlock из 4.

  • Действия над 4 не происходят до доступа к 5, даже если основной поток ожидает завершения других задач. Это связано с тем, что доступ к 5 не синхронизирован с o.lock(), поэтому основной поток может по-прежнему видеть устаревшие данные.

Вопрос. Правильно ли я понимаю?


person afsantos    schedule 19.06.2013    source источник


Ответы (1)


В: Есть ли какая-либо гарантия того, что действия в 0 будут видимыми, происходящими до, параллельным доступом в 3, или я должен объявить переменные как volatile?

Да есть гарантия. Вам не нужен блок synchronized в основном потоке, потому что существует отношение "происходит до" при запуске потоков. Из JLS 17.4.5: "Вызов start() в потоке происходит до любых действий в запущенном потоке".

Это также означает, что если вы передадите свой o в конструктор потока, вам также не понадобится блок synchronized вокруг (3).

Действия по (4) логически происходят после (3), но не происходит-до отношения от (3) до (4), как следствие синхронизации на другом мониторе.

Да и нет. Логический порядок означает, что в одной и той же цепочке обязательно есть отношение «происходит до», даже если это другой монитор. Компилятор не может переупорядочить 3 после 4, даже если они имеют дело с разными мониторами. То же самое было бы верно с доступом к полю volatile.

С несколькими потоками, поскольку (3) только читает объект, тогда нет состояния гонки. Однако, если (3) вносил изменения в объект (а не просто читал его), то в другом потоке эти изменения могут быть не видны в (4). Как вы цитируете, а @StephenC повторяет, JLS говорит, что отношения «происходит до» гарантируются только на одном и том же мониторе. JLS 17.4.5: "Разблокировка монитора происходит перед каждой последующей блокировкой этого монитора".

Пункт выше остался бы верным, даже если бы была синхронизация на sharedMonitor после разблокировки (4).

См. выше.

Действия над (4) не выполняются до доступа к (5), даже если основной поток ожидает завершения других задач.

Нет. Как только основной поток вызывает thread.join() и возвращается без прерывания, основной поток полностью синхронизируется с памятью потока, к которому он присоединился. Между присоединяемым потоком и потоком, выполняющим присоединение, существует отношение «происходит до». JLS 17.4.5: "Все действия в потоке выполняются до того, как какой-либо другой поток успешно вернется из функции join() в этом потоке".

person Gray    schedule 19.06.2013
comment
+1 В конце концов, то, на чем вы синхронизируетесь, не имеет значения. Существует глобальная взаимосвязь. В последнем случае ожидающий поток делает это, чтобы присоединиться к фоновому потоку. - person Peter Lawrey; 19.06.2013
comment
JLS 17.4.5. Разблокировка монитора происходит перед каждой последующей блокировкой этого монитора. - person Stephen C; 19.06.2013
comment
Хорошо, я переформулировал свой ответ @StephenC. Дайте мне знать, если это имеет больше смысла. - person Gray; 19.06.2013
comment
Спасибо, это ответило на мой вопрос. Однако я должен был уточнить, что основной поток не вызывает thread.join(). Он ожидает Future.get(), возвращенного исполнителем. Кроме того, результатом Future.get() не является общий объект o. Но я предполагаю, что принцип остается прежним, поскольку действия, выполняемые асинхронным вычислением, представленным Future, происходят до действий, следующих за получением результата через Future.get() в другом потоке. - person afsantos; 19.06.2013