Почему 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 ответ:
Причина этого заключается в том, что когда вы вставляете
int
вInteger
, Вы можете создать или не создать новый экземплярInteger
. Если это так, то новый экземпляр может не иметь равенства ссылок с другими экземплярамиInteger
, даже если они имеют одинаковое значение.AtomicReference.compareAndSet()
использует равенство ссылок (идентичность) для сравнений.Ключ заключается в том, как компилятор обрабатывает автоматическую блокировку значений
int
: он выдает вызовыInteger.valueOf()
. В качестве оптимизации,Integer.valueOf()
имеет кэш коробочных целых чисел, и по по умолчанию этот кэш содержит значения до 128. Если вы дважды вставите целое числоn
, вы получите одну и ту же ссылкуInteger
каждый раз, если значение было достаточно маленьким, чтобы быть в кэше; в противном случае вы получите два отдельных экземпляра.В настоящее время вы распаковываете старое значение, вычисляете новое значение, и при вызове
compareAndSet()
вы снова вставляете старое значение . Как только вы нажмете 128, вы перестанете получать кэшированные значения, так что вторая коробочная копия больше не будет той же самой, которая находится вAtomicReference
.