如何自动比较和设置不同的变量?

时间:2015-04-21 23:02:00

标签: java concurrency

当我想对单个变量进行比较和设置时,它非常简单:

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在执行我的关键部分的整个过程中保持“开启”,这种解决方案是必要的。其他线程读取状态很好,但重要的是它不能改变。

在这个例子中,“左点”代码相当快,但在我的实际应用中,synchronized块可能会大得多。另外,它使我在简单的turnOn方法中包含同步,而不是使用现有的比较和设置逻辑。

3 个答案:

答案 0 :(得分: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));
}

答案 1 :(得分: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();
  }
}

答案 2 :(得分: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() pointLeft()块。
  2. synchronized在线程进入synchronized turnOn()块{/ 1}}之前完成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是一个原子操作,所以在并发场景中我们仍然只有两种可能的场景:

    1. get
    2. 之前成功完成compareAndSet的调用
    3. get
    4. 之前未成功完成turnOn的调用

      在任何一种情况下,由于state不会更改State.ON,如果至少有一个线程在其先前状态为{{{{}}时调用turnOn(),则它将包含State.OFF 1}}。

      所以结果完全相同,我们不需要在这里使用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,因此这种潜在的重叠无效,因此,对于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执行as-if-atomic,同时真正原子关于在同一个互斥锁上同步的所有其他代码。