← Назад к вопросам
Как работать с примитивами в многопоточности
1.8 Middle🔥 181 комментариев
#JVM и управление памятью#Многопоточность
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как работать с примитивами в многопоточности
Работас примитивами в многопоточной среде — это одна из самых сложных и опасных задач в Java. Примитивы обычно хранятся в стеке и не являются потокобезопасными, поэтому нужны специальные техники и классы.
1. Проблема: Visibility и Race Condition
public class Counter {
private int count = 0; // НЕ потокобезопасно!
public void increment() {
count++; // Это не атомная операция!
// На самом деле это:
// 1. Прочитать count
// 2. Увеличить на 1
// 3. Записать обратно
}
public int getCount() {
return count; // Могу видеть устаревшее значение
}
}
// Результат: потеря обновлений при параллельном доступе
// Thread1 читает: 0, увеличивает на 1, пишет: 1
// Thread2 читает: 0, увеличивает на 1, пишет: 1
// Ожидали 2, получили 1
2. Решение 1: volatile для visibility
public class Counter {
private volatile int count = 0;
public void increment() {
count++; // Всё ещё НЕ атомная операция!
}
public int getCount() {
return count; // Гарантия видеть последнее значение
}
}
// volatile гарантирует:
// 1. Видимость изменений в других потоках
// 2. Запрещает оптимизации компилятором
// НО: не гарантирует атомарность count++
3. Решение 2: synchronized для атомарности
public class Counter {
private int count = 0;
public synchronized void increment() {
count++; // Теперь атомная операция
}
public synchronized int getCount() {
return count;
}
}
// synchronized гарантирует:
// 1. Только один поток может быть в методе
// 2. Visibility всех переменных
// НО: может быть медленно из-за lock contention
4. Решение 3: AtomicInteger (рекомендуется)
Это наилучший способ для примитивов:
Import java.util.concurrent.atomic.AtomicInteger;
public class Counter {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet(); // Атомная операция, быстрая
}
public int getCount() {
return count.get();
}
}
// Методы AtomicInteger:
count.get(); // Прочитать
count.set(5); // Установить
count.incrementAndGet(); // ++count, вернуть новое значение
count.getAndIncrement(); // count++, вернуть старое значение
count.addAndGet(10); // count += 10
count.getAndAdd(10); // count += 10, вернуть старое
count.compareAndSet(0, 1); // Атомная CAS операция
5. Другие Atomic классы для примитивов
import java.util.concurrent.atomic.*;
// Для long значений
AtomicLong duration = new AtomicLong(0);
duration.getAndAdd(100);
// Для boolean
AtomicBoolean isRunning = new AtomicBoolean(true);
isRunning.set(false);
if (isRunning.compareAndSet(true, false)) {
// Операция выполнена только если было true
}
// Для любого объекта/примитива обёрнутого в объект
AtomicReference<Integer> ref = new AtomicReference<>(0);
ref.getAndSet(5);
6. CAS операции (Compare-And-Swap)
Это основа для безблокировки (lock-free) программирования:
AtomicInteger value = new AtomicInteger(10);
// CAS: если текущее значение 10, установить 20, вернуть true
boolean success = value.compareAndSet(10, 20);
if (success) {
System.out.println("Успешно обновили с 10 на 20");
}
// Практический пример: retry loop
public void updateValue(int delta) {
while (true) {
int oldValue = value.get();
int newValue = oldValue + delta;
if (value.compareAndSet(oldValue, newValue)) {
break; // Успех
}
// Иначе retry
}
}
7. Проблемы с примитивами: ABA Problem
AtomicInteger counter = new AtomicInteger(10);
// Thread1
int oldValue = counter.get(); // Читает 10
// Контекст переключился на Thread2
// Thread2
counter.set(20);
counter.set(10);
// Контекст вернулся на Thread1
// Thread1
if (counter.compareAndSet(oldValue, 15)) {
// ОПАСНО! Значение "выглядит" как было 10, но были изменения
}
// Решение: использовать AtomicStampedReference
AtomicStampedReference<Integer> ref = new AtomicStampedReference<>(10, 0);
int[] stampHolder = new int[1];
int value = ref.get(stampHolder);
int stamp = stampHolder[0];
ref.compareAndSet(value, 15, stamp, stamp + 1);
8. Практические примеры
Счётчик параллельных операций:
public class ThreadSafeCounter {
private final AtomicInteger count = new AtomicInteger(0);
private final AtomicInteger activeThreads = new AtomicInteger(0);
public void startTask() {
activeThreads.incrementAndGet();
}
public void completeTask() {
count.incrementAndGet();
activeThreads.decrementAndGet();
}
public int getTotalCompleted() {
return count.get();
}
public int getActiveCount() {
return activeThreads.get();
}
}
Флаг для управления потоками:
public class Service {
private final AtomicBoolean running = new AtomicBoolean(false);
public boolean start() {
return running.compareAndSet(false, true);
}
public void stop() {
running.set(false);
}
public void doWork() {
if (running.get()) {
// Выполнить работу
}
}
}
Счётчик с максимумом:
public class LimitedCounter {
private final AtomicInteger count = new AtomicInteger(0);
private final int limit;
public LimitedCounter(int limit) {
this.limit = limit;
}
public boolean tryIncrement() {
while (true) {
int oldValue = count.get();
if (oldValue >= limit) {
return false; // Лимит достигнут
}
if (count.compareAndSet(oldValue, oldValue + 1)) {
return true;
}
}
}
}
9. Сравнение подходов
// 1. volatile + synchronized (медленно, но просто)
private volatile int count = 0;
public synchronized void increment() { count++; }
// 2. synchronized в целом (медленнее, чем atomic)
private int count = 0;
public synchronized void increment() { count++; }
// 3. AtomicInteger (быстро и просто) ✅
private AtomicInteger count = new AtomicInteger(0);
public void increment() { count.incrementAndGet(); }
10. Когда NOT использовать Atomic
// Если нужна синхронизация нескольких переменных
public class Account {
private final AtomicLong balance = new AtomicLong(0);
private final AtomicLong lastUpdate = new AtomicLong(0);
// ПРОБЛЕМА: balance и lastUpdate обновляются не атомно!
public void deposit(long amount) {
balance.addAndGet(amount);
lastUpdate.set(System.currentTimeMillis());
// Между этими операциями другой поток может вмешаться
}
// Решение: synchronized для нескольких полей
public synchronized void deposit(long amount) {
balance += amount;
lastUpdate = System.currentTimeMillis();
}
}
В итоге: Для примитивов в многопоточности используй AtomicInteger/AtomicLong — это надёжно, быстро и выразительно. Для нескольких переменных — synchronized блоки. А volatile только для flags и simple visibility случаев.