Реализация addAndGet в классе AtomicInteger

Я просматривал исходный код Java (Java 6) для метода addAndGet в классе AtomicInteger.

Соответствующий код был следующим:

public final int addAndGet(int delta) {
    for (;;) {
        int current = get();
        int next = current + delta;
        if (compareAndSet(current, next))
            return next;
    }
}

Метод compareAndSet вызывает собственный метод для выполнения назначения. В основном есть два вопроса:

  1. Как помогает бесконечный цикл?
  2. Какие могут быть сценарии, при которых условие «if (compareAndSet(current, next))» может вернуть false ? В таком случае код может зайти в бесконечный цикл. Если гарантируется, что compareAndSet всегда будет возвращать «истину», то можем ли мы вообще отказаться от этой проверки?

Аналогичные сомнения и по методам decrementAndGet, getAndDecrement, getAndAdd.


person Sandipan Aich    schedule 17.07.2013    source источник


Ответы (1)


Как помогает бесконечный цикл?

Это означает: повторяйте попытку, пока не сработает. Без цикла это может не сработать с первого раза (см. ниже).

Какие могут быть сценарии, при которых условие «if (compareAndSet(current, next))» может вернуть false ?

Это происходит, если два потока пытаются изменить значение одновременно. Один из них доберется туда первым. Другой потерпит неудачу.

Представьте, что два потока (A и B) пытаются увеличить число с 5 до 6.

A: int current = get();  // current = 5
B: int current = get();  // current = 5
B: int next = current + delta;  // next = 6
B: if (compareAndSet(current, next))  // OK
          return next;
A: int next = current + delta;  // next = 6 
A: if (compareAndSet(current, next))  
    // fails, because "current" is still 5
    // and that does not match the value which has been changed to 6 by B

Обратите внимание, что весь смысл этого класса в том, чтобы избежать блокировок. Вместо этого у вас есть этот «оптимистичный контроль валюты»: просто предположите, что никто другой не работает с данными в то же время, и если это окажется неправильным, откатитесь и повторите попытку.

В таком случае код может зайти в бесконечный цикл.

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

Поток A сверху во второй итерации:

A: int current = get();  => current now 6
A: int next = current + delta;  => next = 7
A: if (compareAndSet(current, next))  => now OK

Вполне возможно, что один поток будет ждать вечно, если другие потоки будут постоянно обновлять значение, но только в этом случае. Чтобы избежать этого, вам понадобится некоторое определение «справедливости» (которое поддерживают некоторые другие инструменты в пакете параллелизма).

person Thilo    schedule 17.07.2013