← Назад к вопросам

Будет ли переменная общей, если не помечать ее volatile?

3.0 Senior🔥 111 комментариев
#Многопоточность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Будет ли переменная общей без volatile?

Коротко: Да, переменная будет общей, но с проблемами видимости

Переменная без модификатора volatile остаётся общей (shared) между потоками, но возникают серьёзные проблемы с видимостью изменений (visibility).

Проблема видимости без volatile

public class SharedVariable {
    private int counter = 0; // БЕЗ volatile - опасно!
    
    public void increment() {
        counter++;
    }
    
    public int getCounter() {
        return counter;
    }
}

Что происходит:

  1. Поток A пишет новое значение counter в свой локальный кэш процессора
  2. Поток B может прочитать старое значение из своего кэша
  3. Обновление может быть не видно другому потоку долгое время (или никогда)
  4. Результаты непредсказуемы, race conditions

Решение: volatile гарантирует видимость

public class SharedVariable {
    private volatile int counter = 0; // Правильно!
    
    public void increment() {
        counter++;
    }
    
    public int getCounter() {
        return counter;
    }
}

volatile обеспечивает:

  • Visibility: Изменения видны всем потокам сразу
  • Ordering: Операции с volatile переменной выполняются в правильном порядке
  • Atomicity: Чтение/запись гарантированно атомарны (для примитивных типов)

Альтернативы volatile

1. Синхронизация (synchronized)

private int counter = 0;

public synchronized void increment() {
    counter++;
}

public synchronized int getCounter() {
    return counter;
}

Даёт видимость + atomicity, но дороже по производительности.

2. AtomicInteger

private AtomicInteger counter = new AtomicInteger(0);

public void increment() {
    counter.incrementAndGet();
}

public int getCounter() {
    return counter.get();
}

Лучший выбор для счётчиков и флагов.

Важное: volatile НЕ гарантирует atomicity операций

private volatile int counter = 0;

public void increment() {
    counter++; // ОПАСНО! counter++ - это 3 операции:
               // 1. Прочитать counter
               // 2. Добавить 1
               // 3. Записать обратно
               // Между ними может произойти переключение потока
}

Для таких случаев используй AtomicInteger или synchronized.

Когда использовать volatile

Подходит:

  • Простые флаги: volatile boolean isShutdown
  • Счётчики с простым присваиванием: volatile long lastUpdateTime
  • Double-checked locking паттерн

НЕ подходит:

  • Операции типа counter++ (используй AtomicInteger)
  • Комплексные состояния (используй synchronized или Lock)

Гарантии Java Memory Model

volatile создаёт «happens-before» отношения:

  • Запись в volatile → видна после чтения из volatile любым потоком
  • Гарантируется порядок выполнения операций
private volatile boolean ready = false;
private int value = 0; // обычная переменная

// Поток 1
public void publish() {
    value = 42;      // запись в обычную переменную
    ready = true;    // запись в volatile (happens-before)
}

// Поток 2
public void consume() {
    while (!ready);  // чтение volatile
    System.out.println(value); // Гарантированно выведет 42!
}

Итог

Переменная без volatile остаётся общей, но потоки могут видеть устаревшие значения из кэша. Это вызывает баги, которые сложно воспроизвести. Для многопоточного кода всегда явно указывай volatile, synchronized или используй concurrency utilities (AtomicInteger, ConcurrentHashMap и т.д.).