Ваш код, опубликованный в вашем вопросе, не требует какой-либо атомной модификации, так как атомарное считывание обоих значений не может быть вызвано атомарностью модификации.
Давайте посмотрим на два метода (после установления вашей ошибки использования контента изменяемый переменной как мьютекс):
void turnOn() {
synchronized(this) {
if (state == state.OFF) state = State.ON
}
}
void pointLeft() {
synchronized(this) {
if (state == State.ON) {
direction = Direction.LEFT;
}
}
}
относительно одновременных вызовов в turnOn()
не будет никакой разницы в результатах.Если как методы вызываются одновременно, существуют два сценария:
- нить умудряется завершить
synchronized
блок turnOn()
перед synchronized
блока pointLeft()
будет введен
- все попытки
pointLeft()
завершить synchronized
блок перед тем, как поток входит в блок synchronized
turnOn()
В любом случае, state
будет содержать значение State.ON
, если хотя бы один поток вызван turnOn()
(при условии, что состояние не было BROKEN
), а direction
либо будет изменено на Direction.LEFT
, либо останется нетронутым на основе либо непредсказуемой синхронизации потоков, либо межпоточных зависимостей вне объем этого класса.
Теперь, что произойдет, если мы используем AtomicReference
, как в вашей первой версии?
final AtomicReference<State> state = new AtomicReference<>(State.OFF);
void turnOn() { state.compareAndSet(State.OFF, State.ON); }
void pointLeft() { if(state.get() == State.ON) direction = Direction.LEFT; }
AtomicReference.get
является атомарной операцией, поэтому в параллельном сценарии у нас еще есть только два возможных сценария:
- призывание
compareAndSet
успешно завершена до get
- Нет призывание
compareAndSet
успешно не завершена до get
И в любом случай, так как turnOn
не изменится state
, он будет содержать State.ON
, если хотя бы один поток вызвал turnOn()
, когда его предыдущее состояние было State.OFF
.
Итак, результат точно такой же, и здесь не нужно использовать блоки synchronized
. Если pointLeft()
более сложный метод и получает доступ к state
несколько раз, вы можете просто прочитать значение один раз в начале:
void pointLeft() {
State currentState = state.get();
if(currentState == State.ON) {
direction = Direction.LEFT;
// possibly more code, may use currentState
}
// possibly more code, may use currentState
}
Теперь state
до сих пор читают атомарно и метод будет действовать соответствующим образом. Другие потоки, изменяющие state
, могут перекрываться с последующим выполнением pointLeft()
, но поскольку pointLeft()
не коснется state
, это потенциальное перекрытие не имеет эффекта, таким образом, метод будет вести себя так, как если бы атомически относился к переменной , фактически не блокируя потоки.
Обратите внимание, что если есть и другие причины для использования семафоров, то есть из-за других общих переменных, вы все равно можете комбинировать synchronized
с атомарной переменной:
final AtomicReference<State> state = new AtomicReference<>(State.OFF);
void turnOn() {
state.compareAndSet(State.OFF, State.ON);
}
void pointLeft() {
synchronized(this) { // no concurrent pointLeft() execution
State currentState = state.get();
if(currentState == State.ON) {
direction = Direction.LEFT;
// possibly more code, may use currentState
}
// possibly more code, may use currentState
}
}
turnOn()
еще не будет заблокирован, и, таким образом, мы должны обеспечить, чтобы другие методы точно читали state
и действовали как-будто-атоматив относительно этого state
, будучи действительно атомарным относительно всей другой синхронизации кода на одном и том же мьютексе.
Если вам нужно синхронизировать, вам необходимо синхронизировать.Если нить не может читать значения во время их изменения, это требование. Вы не можете это изменить. Или есть еще какое-то требование? –
Другие темы могут читать, но не должны писать. Я уточню в вопросе. – MikeD
['ReentrantReadWriteLock'] (https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.html). Нет? –