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

Какую проблему не решает volatile?

1.3 Junior🔥 161 комментариев
#Многопоточность

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

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

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

Проблемы, которые НЕ решает volatile в Java

Ключевое слово volatile в Java решает только одну узкую проблему — обеспечивает видимость изменений переменной между потоками. Однако существует множество других проблем в многопоточном программировании, которые volatile не решает.

Основная проблема: Race conditions и atomicity

Самая критичная проблема, которую не решает volatile — это race conditions. Volatile гарантирует только видимость, но НЕ гарантирует атомарность операций.

Классический пример — счётчик:

public class Counter {
    private volatile int count = 0;  // volatile НЕ поможет!
    
    public void increment() {
        count++;  // Это НЕ атомарная операция!
    }
    
    public int getCount() {
        return count;
    }
}

Почему это опасно:

  1. count++ раскладывается на 3 операции:

    • READ: получи текущее значение count
    • MODIFY: увеличь на 1
    • WRITE: запиши новое значение
  2. При одновременном доступе двух потоков:

    Поток 1: READ (count=5) → MODIFY → WRITE (count=6)
    Поток 2: READ (count=5) → MODIFY → WRITE (count=6)
    
    Результат: count=6 (вместо ожидаемых 7!)
    Это race condition — потеря одного инкремента.
    
  3. Volatile гарантирует что оба потока видят одно и то же значение, но не предотвращает одновременное изменение.

Правильное решение — AtomicInteger

public class Counter {
    private AtomicInteger count = new AtomicInteger(0);
    
    public void increment() {
        count.incrementAndGet();  // Атомарная операция
    }
    
    public int getCount() {
        return count.get();
    }
}

Проблема 2: Compound operations

Volatile не защищает составные операции, требующие консистентности нескольких полей:

public class Account {
    private volatile double balance = 1000.0;
    private volatile int transactionCount = 0;
    
    public void transfer(double amount) {
        // Это НЕ атомарно!
        balance -= amount;
        transactionCount++;
    }
}

Проблема: поток может видеть обновленный balance, но старый transactionCount, или наоборот.

Решение — synchronized:

public synchronized void transfer(double amount) {
    balance -= amount;
    transactionCount++;
}

Проблема 3: Check-then-act паттерн

Volatile не решает check-then-act race conditions:

public class LazyHolder {
    private volatile Expensive instance;
    
    public Expensive getInstance() {
        if (instance == null) {  // Check
            instance = new Expensive();  // Act
        }
        return instance;
    }
}

Рace condition: два потока могут одновременно пройти проверку if (instance == null) и оба создадут объект.

Решение — double-checked locking:

public Expensive getInstance() {
    if (instance == null) {
        synchronized (this) {
            if (instance == null) {
                instance = new Expensive();
            }
        }
    }
    return instance;
}

Или проще — используй класс-holder:

private static class InstanceHolder {
    static final Expensive INSTANCE = new Expensive();
}

public static Expensive getInstance() {
    return InstanceHolder.INSTANCE;
}

Проблема 4: Deadlocks

Volatile совершенно не помогает предотвратить deadlocks:

public class DeadlockExample {
    private volatile Object lock1 = new Object();
    private volatile Object lock2 = new Object();
    
    public void method1() {
        synchronized (lock1) {
            Thread.sleep(100);
            synchronized (lock2) {  // Может быть deadlock!
                // ...
            }
        }
    }
    
    public void method2() {
        synchronized (lock2) {
            Thread.sleep(100);
            synchronized (lock1) {  // Deadlock!
                // ...
            }
        }
    }
}

Volatile здесь не поможет. Нужно правильное упорядочивание блокировок.

Проблема 5: Memory barriers между разными потоками

Volatile гарантирует видимость через happens-before, но не гарантирует временной порядок:

public class VisibilityIssue {
    private volatile boolean ready = false;
    private int value = 0;  // НЕ volatile!
    
    public void write() {
        value = 42;      // Запись 1
        ready = true;    // Запись 2
    }
    
    public void read() {
        if (ready) {
            System.out.println(value);  // Гарантирует видеть 42
        }
    }
}

Записание value БЕЗ volatile обычно работает из-за happens-before семантики ready, но это опасно.

Проблема 6: Out-of-order execution и CPU cache

Volatile запрещает некоторые оптимизации, но не все. На современных CPU могут быть сложные ситуации:

public class ReorderingProblem {
    private volatile int x = 0;
    private volatile int y = 0;
    
    public void thread1() {
        x = 1;
        int r1 = y;  // Может прочитать 0 (на некоторых CPU)
    }
    
    public void thread2() {
        y = 1;
        int r2 = x;  // Может прочитать 0
    }
}

Этот код может привести к r1=0 и r2=0 на слабых memory models.

Проблема 7: Data corruption при работе с non-atomic операциями

public class LongVolatile {
    private volatile long value = 0;  // volatile поможет немного
    
    public void increment() {
        value++;  // Все еще race condition!
    }
}

Даже для long с volatile есть race condition в операции increment.

Когда volatile достаточен

Volatile решает проблему ТОЛЬКО в этих случаях:

  1. Чтение и запись одного поля (не compound):

    private volatile boolean flag = false;
    
  2. Immutable объекты в volatile поле:

    private volatile String immutableValue;
    
  3. Status flags:

    private volatile boolean shutdownRequested = false;
    

Таблица: Какая проблема каким инструментом решается

ПроблемаvolatilesynchronizedAtomicLock
Видимость между потоками
Atomicity операций
Race conditions
Compound операции
Check-then-act
Deadlock prevention
Performance?

Практическое правило

Если у тебя есть сомнения — не используй volatile. Используй:

  • AtomicInteger, AtomicLong, AtomicReference для единственного atomic значения
  • synchronized для простых блокировок
  • ReentrantLock для сложных сценариев
  • ConcurrentHashMap для потокобезопасных коллекций

Volatile — это очень узкий инструмент для очень конкретных проблем.