AtomicInteger.getAndIncrement(): примеры (JAVA)

AtomicInteger.getAndIncrement() - разбор и примеры
Раздел: Многопоточность, Атомарные классы
AtomicInteger.getAndIncrement(): int

Описание и поведение метода

Метод AtomicInteger.getAndIncrement() из пакета java.util.concurrent.atomic выполняет атомарное приращение целочисленного значения на 1 и возвращает прежнее значение. Работает без аргументов. Тип возвращаемого значения - примитив int, который соответствует значению до увеличения.

Семантика операции соответствует атомарному read-modify-write: чтение текущего значения и попытка установить значение на (текущее + 1) с использованием механизма CAS. Операция видима для других потоков как атомарная модификация, то есть обеспечивает безопасную инкрементацию в многопоточной среде без явных блокировок.

Аргументы: отсутствуют. Возвращаемые значения: целое число - предыдущее значение счётчика, возможно отрицательное или переполненное при выходе за пределы диапазона int (переполнение происходит по правилам Java, то есть wrap-around).

Типовые сценарии применения: генерация уникальных последовательных идентификаторов в многопоточном окружении, реализация конкурентных счётчиков, замена небезопасных операций типа i++ в многопоточных приложениях.

Важно различать getAndIncrement() и incrementAndGet(): первый возвращает старое значение, второй возвращает новое значение после инкремента.

Короткие примеры использования

Примеры демонстрируют базовое поведение и отличие от обычного инкремента в многопоточности.

// Пример 1: одиночный поток
java.util.concurrent.atomic.AtomicInteger ai = new java.util.concurrent.atomic.AtomicInteger(5);
int prev = ai.getAndIncrement();
System.out.println(prev + ", " + ai.get());
5, 6
// Пример 2: отличие от incrementAndGet
java.util.concurrent.atomic.AtomicInteger ai2 = new java.util.concurrent.atomic.AtomicInteger(0);
int a = ai2.getAndIncrement();
int b = ai2.incrementAndGet();
System.out.println(a + ", " + b + ", " + ai2.get());
0, 2, 2
// Пример 3: конкурентная инкрементация (упрощённо)
java.util.concurrent.atomic.AtomicInteger counter = new java.util.concurrent.atomic.AtomicInteger(0);
java.util.concurrent.ExecutorService ex = java.util.concurrent.Executors.newFixedThreadPool(3);
for (int i = 0; i < 100; i++) {
    ex.submit(() -> counter.getAndIncrement());
}
ex.shutdown();
ex.awaitTermination(1, java.util.concurrent.TimeUnit.SECONDS);
System.out.println(counter.get());
100

Аналоги в Java и нюансы

Похожие методы в Java и их особенности:

  • incrementAndGet() - возвращает значение после инкремента. Используется, когда нужен уже увеличенный результат.
  • getAndAdd(int delta) - атомарно прибавляет delta и возвращает предыдущее значение. Эквивалент getAndIncrement() при delta = 1.
  • addAndGet(int delta) - прибавляет delta и возвращает новое значение.
  • getAndUpdate(IntUnaryOperator) и updateAndGet(IntUnaryOperator) (Java 8) - атомарные обновления по функции. Полезны для более сложных трансформаций.
  • AtomicLong и LongAdder - альтернативы для больших счётчиков: AtomicLong похож по API; LongAdder эффективнее при высокой конкуренции, но не обеспечивает атомарного возврата старого значения.

Выбор: если нужен атомарный старый результат - getAndIncrement; если нужен новый - incrementAndGet; при высокой конкуренции и если точная атомарная выдача старого значения не требуется - рассмотреть LongAdder.

Аналоги в других языках и отличия

Короткие соответствия и отличия с примерами.

JavaScript (Atomics)

// SharedArrayBuffer + Atomics
const sab = new SharedArrayBuffer(4);
const arr = new Int32Array(sab);
arr[0] = 0;
const old = Atomics.add(arr, 0, 1);
console.log(old, arr[0]);
0 1

C# (Interlocked)

// Interlocked.Increment возвращает новое значение
int x = 0;
int newVal = System.Threading.Interlocked.Increment(ref x);
Console.WriteLine(newVal + ", " + x);
// Чтобы получить прежнее значение атомарно, требуется цикл CAS
1, 1
// CAS для прежнего значения в C#
int GetAndIncrement(ref int v) {
  int oldVal;
  do {
    oldVal = v;
  } while (System.Threading.Interlocked.CompareExchange(ref v, oldVal + 1, oldVal) != oldVal);
  return oldVal;
}

Python

# В CPython простая операция может вести себя атомарно из-за GIL, но для переносимости используется Lock
import threading
x = 0
lock = threading.Lock()
with lock:
    old = x
    x += 1
print(old, x)
0 1

Go

// sync/atomic
import "sync/atomic"
var v int32 = 0
old := atomic.AddInt32(&v, 1) - 1
fmt.Println(old, atomic.LoadInt32(&v))
0 1

Kotlin

// Используется java.util.concurrent.atomic.AtomicInteger
val ai = java.util.concurrent.atomic.AtomicInteger(0)
val prev = ai.getAndIncrement()
println("$prev, ${ai.get()}")
0, 1

PHP

// В чистом PHP нет атомарного примитива; в расширении Swoole есть Swoole\Atomic
$atomic = new Swoole\Atomic(0);
$old = $atomic->get();
$atomic->add(1);
echo $old, ", ", $atomic->get();
0, 1

SQL (Postgres)

-- Возврат нового значения
UPDATE counters SET val = val + 1 WHERE id = 1 RETURNING val;
 val
-----
  1

Lua

-- В Lua нет встроенных атомиков; в многопоточной среде требуется внешняя синхронизация
-- или реализация в C. Пример с mutex в фреймворке используется аналогично lock.
(зависит от реализации окружения)

Итог: некоторые языки предоставляют встроенные атомарные операции с разной семантикой возврата (предыдущее или новое значение). В Java getAndIncrement возвращает предыдущее значение, тогда как в C# Interlocked.Increment возвращает новое значение; JS Atomics.add возвращает старое значение, сходно с Java.

Типичные ошибки и неверные ожидания

Основные ошибки при использовании метода:

  • Путаница с возвращаемым значением: ожидание нового значения вместо старого. getAndIncrement() возвращает прежнее значение; для нового используется incrementAndGet().
  • Ожидание параметров: метод без аргументов. Для приращения на произвольную дельту используется getAndAdd(delta).
  • Использование AtomicInteger в случаях, когда требуется комбинированная атомарность по нескольким полям. AtomicInteger обеспечивает атомарность лишь для одного значения; для сложных транзакций требуется внешний механизм синхронизации или lock-free алгоритм с несколькими CAS.
  • Игнорирование переполнения: при достижении Integer.MAX_VALUE инкремент приведет к отрицательному числу по правилам Java.
  • Ожидание одинаковой производительности: в условиях высокой конкуренции LongAdder может быть эффективнее, но он не предоставляет атомарного возврата старого значения.
// Ошибка: ожидание нового значения
AtomicInteger ai = new AtomicInteger(0);
int x = ai.getAndIncrement();
// разработчик думает, что x == 1
System.out.println(x);
0

Изменения и эволюция API

Метод getAndIncrement() присутствует в API с Java 5 и не претерпевал изменений в поведении. В Java 8 были добавлены функции getAndUpdate и updateAndGet, которые расширили возможности атомарных обновлений функциональными интерфейсами. В Java 9 введены VarHandle как более гибкая альтернатива низкоуровневому доступу к полям; VarHandle предоставляет собственные атомарные операции, но getAndIncrement() как метод AtomicInteger не изменился.

Рекомендации: при модернизации кода рассмотреть VarHandle или LongAdder в случае особых требований по производительности, но API AtomicInteger остаётся стабильным и совместимым.

Расширенные и редкие сценарии применения

Ниже приведены несколько подробных примеров с пояснениями.

Пример java
// 1) Генератор уникальных id в многопоточном сервере
public class IdGenerator {
    private final java.util.concurrent.atomic.AtomicInteger seq = new java.util.concurrent.atomic.AtomicInteger(1);
    public int nextId() {
        return seq.getAndIncrement(); // возвращает уникальный id (старое значение)
    }
}
// Результат при вызове nextId() последовательно: 1, 2, 3, ...
(последовательность чисел 1, 2, 3 ... в зависимости от вызовов)
Пример java
// 2) Ограниченный счётчик - реализация с проверкой верхнего предела через CAS
java.util.concurrent.atomic.AtomicInteger c = new java.util.concurrent.atomic.AtomicInteger(0);
int limit = 10;
int prev;
do {
    prev = c.get();
    if (prev >= limit) {
        // достигнут предел - возвращаем маркер или бросаем исключение
        break;
    }
} while (!c.compareAndSet(prev, prev + 1));
// prev содержит прежнее значение либо последнее наблюдаемое
(в зависимости от состояния c; после успешного CAS c увеличится на 1)
Пример java
// 3) Сравнение с небезопасным инкрементом (демонстрация гонки)
public class RaceDemo {
    static int unsafe = 0;
    static java.util.concurrent.atomic.AtomicInteger safe = new java.util.concurrent.atomic.AtomicInteger(0);
    public static void main(String[] args) throws InterruptedException {
        var ex = java.util.concurrent.Executors.newFixedThreadPool(10);
        for (int i = 0; i < 1000; i++) {
            ex.submit(() -> { unsafe++; safe.getAndIncrement(); });
        }
        ex.shutdown();
        ex.awaitTermination(1, java.util.concurrent.TimeUnit.SECONDS);
        System.out.println("unsafe=" + unsafe + ", safe=" + safe.get());
    }
}
unsafe может быть меньше 1000 из-за потерь при гонке, safe == 1000
Пример java
// 4) Использование getAndUpdate для условного инкремента с возвратом старого значения
java.util.concurrent.atomic.AtomicInteger a = new java.util.concurrent.atomic.AtomicInteger(0);
int old = a.getAndUpdate(x -> x < 5 ? x + 1 : x);
// Если x < 5, то значение увеличится, и old будет предыдущим
old зависит от текущего a; новое значение станет в пределах 0..5
Пример java
// 5) Интеграция с LongAdder при подсчёте событий и выдаче порядковых номеров
// LongAdder используется для подсчёта объёма (более эффективно при высоком контеншне),
// AtomicInteger используется для выдачи уникальных индексов
java.util.concurrent.atomic.LongAdder hits = new java.util.concurrent.atomic.LongAdder();
java.util.concurrent.atomic.AtomicInteger idGen = new java.util.concurrent.atomic.AtomicInteger(0);
// при обработке события:
hits.increment();
int id = idGen.getAndIncrement();
// hits.sum() - суммарное количество событий; id - уникальный индекс обработанного события
hits.sum() увеличивается, id уникален для каждого события

Пояснения: в сложных сценариях комбинирование атомиков и CAS-циклов позволяет реализовать пределы, условные изменения и lock-free структуры. Для задач генерации последовательностей getAndIncrement удобен своей семантикой «вернуть старое и сразу увеличить».

джава AtomicInteger.getAndIncrement() function comments

En
AtomicInteger.getAndIncrement() Возвращает старое значение и инкрементирует