2015-04-21 3 views
1

Когда я хочу, чтобы сравнить-и-установить на одной переменной, она довольно проста:Как атомно сравнивать и устанавливать разные переменные?

enum State {ON, OFF, BROKEN}; 
AtomicReference<State> state = new AtomicReference<>(State.OFF); 

void turnOn() { state.compareAndSet(State.OFF, State.ON); } 

Однако, если я хочу сравнить с одной переменной и установить другой, то я должен использовать какой-то другой замок механизм:

enum Direction {LEFT, RIGHT}; 
State state; 
Direction direction; 

void turnOn() { 
    synchronized(state) { 
    if (state == state.OFF) state = State.ON 
    } 
} 

void pointLeft() { 
    synchronized(state) { 
    if (state == State.ON) { 
     direction = Direction.LEFT; 
    } 
    } 
} 

Такое решение необходимо, если мне нужно state остаться «на» все время, делая мою критическую секцию. Для других потоков нормально читать состояние, но важная часть состоит в том, чтобы он не менялся.

В этом примере код «точка-левый» довольно быстр, но в моем фактическом приложении синхронизированный блок потенциально намного больше. Кроме того, это приводит к включению синхронизации в тривиальный метод turnOn вместо использования существующей логики сравнения и установки.

+0

Если вам нужно синхронизировать, вам необходимо синхронизировать.Если нить не может читать значения во время их изменения, это требование. Вы не можете это изменить. Или есть еще какое-то требование? –

+0

Другие темы могут читать, но не должны писать. Я уточню в вопросе. – MikeD

+0

['ReentrantReadWriteLock'] (https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.html). Нет? –

ответ

1

Либо создать синхронизированный метод, который делает оба изменения состояния, или создать непреложный класс экземпляр которого представляет собой двойное состояние и использовать AtomicReference для этого:

public class DirectionState { 
    private final Direction direction; 
    private final State state; 
    // rest of class omitted 
} 


private AtomicReference<DirectionState> ref = new AtomicReference<>(new DirectionState(Direction.LEFT, State.OFF)); 

и использовать его для изменения поля индивидуально:

// locking code removed 
void turnOn() { 
    DirectionState directionState = ref.get(); 
    directionState.compareAndSet(directionState, new DirectionState(directionState.getDirection(), State.ON)); 
} 
// similar for pointLeft() 

и вместе

void set(Direction direction, State state) { 
    ref.set(new DirectionState(direction, state)); 
} 
0

Ваш второй образец кода не будет работать. Вы синхронизируете переменную-член, которую вы меняете. Это означает, что различные потоки потенциально (вероятно?) Получают различные блокировки. Это означает, что код не безопасен вообще.

Вы можете использовать Lock или другой неизменяемый объект для синхронизации.

enum Direction {LEFT, RIGHT}; 
private final Lock lock = new ReentrantLock(); 
State state; 
Direction direction; 

void turnOn() { 
    lock.lock(); 
    try { 
    if (state == state.OFF) state = State.ON 
    } finally { 
    lock.unlock(); 
    } 
} 

void pointLeft() { 
    lock.lock(); 
    try { 
    if (state == State.ON) { 
     direction = Direction.LEFT; 
    } 
    } finally { 
    lock.unlock(); 
    } 
} 
+0

В этой версии 'state' может оставаться' AtomicReference', а 'turnOn' не требует блокировки, не так ли? –

+0

Является ли этот подход, когда тело полного метода заблокировано, отличное от синхронизированных по ключевому слову методов? – MikeD

+0

@MickMnemonic Это действительно зависит от того, какие другие методы существуют в классе, который считывает состояние и направление. Если есть другие методы, которые предполагают, что направление не может измениться, пока состояние не включено, тогда любые чтения или мутации как состояния, так и направления должны быть синхронизированы. –

0

Ваш код, опубликованный в вашем вопросе, не требует какой-либо атомной модификации, так как атомарное считывание обоих значений не может быть вызвано атомарностью модификации.

Давайте посмотрим на два метода (после установления вашей ошибки использования контента изменяемый переменной как мьютекс):

void turnOn() { 
    synchronized(this) { 
    if (state == state.OFF) state = State.ON 
    } 
} 

void pointLeft() { 
    synchronized(this) { 
    if (state == State.ON) { 
     direction = Direction.LEFT; 
    } 
    } 
} 

относительно одновременных вызовов в turnOn() не будет никакой разницы в результатах.Если как методы вызываются одновременно, существуют два сценария:

  1. нить умудряется завершить synchronized блок turnOn() перед synchronized блока pointLeft() будет введен
  2. все попытки pointLeft() завершить synchronized блок перед тем, как поток входит в блок synchronizedturnOn()

В любом случае, 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 является атомарной операцией, поэтому в параллельном сценарии у нас еще есть только два возможных сценария:

  1. призывание compareAndSet успешно завершена до get
  2. Нет призывание 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, будучи действительно атомарным относительно всей другой синхронизации кода на одном и том же мьютексе.

Смежные вопросы