AtomicInteger для генерации ограниченной последовательности - PullRequest
7 голосов
/ 02 июня 2010

Как мы можем использовать AtomicInteger для генерации ограниченной последовательности, скажем, порядковый номер должен быть от 1 до 60. Как только последовательность достигает 60, она должна начинаться снова с 1. Я написал этот код, хотя и не уверен, что это безопасно для потоков или нет?

public int getNextValue()
{
 int v;
 do
 {
   v = val.get();
   if ( v == 60)
   {
    val.set(1);
   }
 }
  while (!val.compareAndSet(v , v + 1));
   return v + 1;
  }

Ответы [ 6 ]

14 голосов
/ 02 июня 2010

Вы могли бы сделать

return val.getAndIncrement() % 60;

Если вы не обеспокоены превышением целочисленного максимального значения (2147483647). Если это проблема, вы можете взглянуть на реализацию getAndIncrement:

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

Все, что вам нужно изменить, это строка int next..., например:

int next = (current + 1) % 60;

К сожалению. Это проходит через 0-> 59. Вам нужно 1-> 60, поэтому добавьте единицу к возвращаемому значению, чтобы получить желаемый результат.

5 голосов
/ 03 февраля 2017

Вы можете сделать это в одну строку, используя Java 8.

AtomicInteger counter = new AtomicInteger();

public int getNextValue() {
    return counter.updateAndGet(n -> (n >= 60) ? 1 : n + 1);
}
1 голос
/ 02 июня 2010

Если вы сделаете метод synchronized, то он будет потокобезопасным, пока к val больше нет доступа. Однако этот подход немного обременителен, я бы переписал его следующим образом:

public synchronized int getNextValue() {
    val.compareAndSet(60, 0); // Set to 0 if current value is 60.
    return val.incrementAndGet();
}

Это дает 1 пока с 60 обратно включительно . Если вам действительно нужно 1 до 59, тогда замените 60 на 59.

0 голосов
/ 02 июня 2010

Быстрый ответ, не потокобезопасен. Тест и набор должны быть атомарными, если только вы не синхронизируете весь метод. Обратите внимание, что val.get () и тест v не являются атомарными. Если поток уступает после v = val.get (), вы получите два вызова с одинаковым порядковым номером.

Кроме того, в случае сбоя compareAndSet вы никогда не измените значения, это будет бесконечный цикл.

AtomicInteger имеет getAndIncrement () вызов. Это вернет вам чистую стоимость.

Катиться немного сложнее. Одним из решений является изменение возвращаемого значения. Примерно так:

int v = val.getAndIncrement();
return (v % 60) + 1;

Поскольку у каждого потока есть локальная копия v, мы можем безопасно выполнить некоторые математические операции и вернуть значение. Есть одно препятствие, если вы получаете переполнение. В зависимости от того, как часто вы генерируете порядковый номер, это может или не может быть проблемой.

0 голосов
/ 02 июня 2010

Есть ли какая-то особая причина использовать AtomicInteger здесь, а не просто синхронизированный метод?

Как насчет чего-то простого, подобного следующему:

private int val=1;

public synchronized int getNextValue() {
 int v=val;
 val = (val==60) ? 1 : (val+1); 
 return v;
}
0 голосов
/ 02 июня 2010

Нет, это не потокобезопасно - вы не должны вызывать set внутри цикла:

int value, next;
do {
    value = val.get();
    next = (value == 60) ? 1 : (value + 1);
} while (!val.compareAndSet(value, next);
return next;
...