Почему AtomicReference CAS возвращает false со значением 128?


Я использовал AtomicReference для реализации AtomicInteger. Однако во время тестирования я заметил, что даже в однопоточной среде операция CAS застряла, как только ее значение достигло 128.. Я делаю что-то неправильно или есть оговорка в AtomicReference (может быть связано с CPU)? Вот мой код:

public class MyAtomInt {
  private final AtomicReference<Integer> ref;

  public MyAtomInt(int init) {
    ref = new AtomicReference<Integer>(init);
  }

  public MyAtomInt() {
    this(0);
  }

  public void inc() {
    while (true) {
      int oldVal = ref.get();
      int nextVal = oldVal + 1;
      boolean success = ref.compareAndSet(oldVal, nextVal); // false once oldVal = 128
      if (success) {
        return;
      }
    }
  }

  public int get() {
    return ref.get();
  }

  static class Task implements Runnable {

    private final MyAtomInt myAtomInt;
    private final int incCount;

    public Task(MyAtomInt myAtomInt, int cnt) {
      this.myAtomInt = myAtomInt;
      this.incCount = cnt;
    }

    @Override
    public void run() {
      for (int i = 0; i < incCount; ++i) {
        myAtomInt.inc();
      }
    }
  }

  public static void main(String[] args) throws Exception {
    MyAtomInt myAtomInt = new MyAtomInt();
    ExecutorService exec = Executors.newSingleThreadExecutor();
    exec.submit(new Task(new MyAtomInt(), 150)).get();
    System.out.println(myAtomInt.get());
    exec.shutdown();
  }
}
1 5

1 ответ:

Причина этого заключается в том, что когда вы вставляете int в Integer, Вы можете создать или не создать новый экземпляр Integer. Если это так, то новый экземпляр может не иметь равенства ссылок с другими экземплярами Integer, даже если они имеют одинаковое значение. AtomicReference.compareAndSet() использует равенство ссылок (идентичность) для сравнений.

Ключ заключается в том, как компилятор обрабатывает автоматическую блокировку значений int: он выдает вызовы Integer.valueOf(). В качестве оптимизации, Integer.valueOf() имеет кэш коробочных целых чисел, и по по умолчанию этот кэш содержит значения до 128. Если вы дважды вставите целое число n, вы получите одну и ту же ссылку Integer каждый раз, если значение было достаточно маленьким, чтобы быть в кэше; в противном случае вы получите два отдельных экземпляра.

В настоящее время вы распаковываете старое значение, вычисляете новое значение, и при вызове compareAndSet() вы снова вставляете старое значение . Как только вы нажмете 128, вы перестанете получать кэшированные значения, так что вторая коробочная копия больше не будет той же самой, которая находится в AtomicReference.