Подтвердить что ты не робот

Почему существует цикл в getAndSet() AtomicInteger и аналогичных классах?

С какой целью в этом коде используется цикл?

public final int getAndSet(int newValue) {
    for (;;) {
        int current = get();
        if (compareAndSet(current, newValue))
            return current;
    }
}
4b9b3361

Ответ 1

Классы AtomicXXX представляют собой атомарные типы данных. Это означает, что они должны возвращать согласованные результаты при одновременном доступе к двум или более потокам. compareAndSet - это операция, которая обычно реализуется непосредственно в аппаратном обеспечении, поэтому getAndSet реализуется в терминах compareAndSet.

Метод работает следующим образом: во-первых, возвращается текущее значение. Теперь возможно, что другой поток одновременно изменяет значение, поэтому его нужно проверить, используя compareAndSet, что это не так. Если другой поток изменил значение, процедура должна быть повторена, поскольку в противном случае возвращается неправильное значение. Следовательно, цикл.

Ответ 2

Существует школа мысли, в которой говорится, что вы должны использовать блокировки так же экономно, как можете. То есть никогда не используйте блокировку, если вы можете ее избежать, и если вы ее используете, заблокируйте ее на минимальное время. Обоснование этого объясняется иногда значительными затратами на блокировку в первую очередь, а также стоимостью одного потока ожидания, в то время как другой удерживает блокировку требуемого ресурса.

Доступно для очень долгое время, инструкции cpu, называемые Compare and Set (или CAS для краткости), предназначенные для помощи в этом что по существу делают:

if (value == providedValue) {
  value = newValue;
  return true;
} else {
  return false;
}

эти инструкции могут выполняться на уровне машинного кода и значительно быстрее создавать блокировку.

Представьте, что вы хотите добавить 1 к номеру с помощью одной из этих инструкций таким образом, чтобы систематически работала корректно при высокой параллельной нагрузке. Очевидно, вы могли бы его кодировать как:

int old = value;
if ( compareAndSet(old, old+1) ) {
  // It worked!
} else {
  // Some other thread incremented it before I got there.
}

Но что мы можем сделать, если CAS не удалось? Вы догадались - повторите попытку!

boolean succeeded = false;
do {
  int old = value;
  if ( compareAndSet(old, old+1) ) {
    // It worked!
    succeeded = true;
  } else {
    // Some other thread incremented it before I got there. Just try again.
  }
} while (!succeeded);

И там вы видите образец, который вы наблюдаете.

Используя эту и подобные идиомы, можно реализовать множество функций и даже некоторые довольно сложные структуры данных без каких-либо блокировок (обычно называемых Lock Free). Например, здесь представляет собой реализацию Lock-Free Ring Buffer.

Ответ 3

С какой целью в этом коде используется цикл?

Легко понять, почему существует цикл for, глядя на то, что может произойти, если его там не было.

Предположим, что метод выглядел так:

int current = get();
compareAndSet(current, newValue);
return current;

Теперь, если другой поток пришел и назвал getAndSet одновременно, он мог бы изменить значение между

int current = get();

и

compareAndSet(current, newValue);

Не удалось выполнить compareAndSet, и этот метод не будет работать так, как предполагалось.

Говоря это, это не единственный правильный способ реализации этого метода. Я предполагаю, что он реализован так, как это происходит из-за эффективности. Вместо того, чтобы приобретать/освобождать блокировку для этой операции, она делегирует compareAndSet, которая, вероятно, реализована с некоторой эффективной CAS-операцией.