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

Для чего нужен volatile?

2.0 Middle🔥 211 комментариев
#Многопоточность

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

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

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

Volatile в Java

Keyword volatile используется для обозначения переменной, которая может быть изменена несколькими потоками и должна быть прочитана/записана через память, а не кэш процессора. Это гарантирует видимость изменений между потоками и исключает некоторые оптимизации компилятора.

Проблема без volatile

Без volatile один поток может кэшировать значение переменной в своём регистре процессора, и другие потоки не будут видеть обновления:

public class StopFlag {
    private boolean running = true;  // БЕЗ volatile
    
    public void start() {
        new Thread(() -> {
            while (running) {
                System.out.println("Working...");
            }
            System.out.println("Stopped");
        }).start();
    }
    
    public void stop() {
        running = false;
    }
}

// Проблема: поток может никогда не заметить, что running = false,
// потому что он кэширует значение

Решение с volatile

public class StopFlag {
    private volatile boolean running = true;  // С volatile
    
    public void start() {
        new Thread(() -> {
            while (running) {
                System.out.println("Working...");
            }
            System.out.println("Stopped");
        }).start();
    }
    
    public void stop() {
        running = false;  // Все потоки немедленно увидят это изменение
    }
}

// Теперь volatile гарантирует, что изменение будет видно всем потокам

Гарантии volatile

1. Visibility — видимость: Если один поток запишет значение в volatile переменную, все остальные потоки немедленно увидят это значение.

2. Ordering — порядок: Чтение и запись в volatile переменную создают точку синхронизации (synchronization point) — операции до них не могут быть переупорядочены после, и наоборот.

public class Example {
    private int x = 0;
    private volatile int y = 0;  // volatile
    
    public void writer() {
        x = 1;        // A
        y = 1;        // B (volatile write)
    }
    
    public void reader() {
        int r1 = y;   // C (volatile read)
        int r2 = x;   // D
    }
}

// Гарантия: если reader увидит y = 1, то он увидит и x = 1
// Потому что операция A (x = 1) happens-before операции B (y = 1),
// а операция B happens-before операции C (r1 = y)

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

1. Флаги синхронизации:

public class Task {
    private volatile boolean isRunning = false;
    
    public void startTask() {
        new Thread(() -> {
            isRunning = true;
            try {
                performWork();
            } finally {
                isRunning = false;
            }
        }).start();
    }
}

2. Поля конфигурации, которые читаются часто:

public class Config {
    private volatile int timeout = 30000;
    private volatile String apiUrl = "https://api.example.com";
    
    public void updateTimeout(int newTimeout) {
        timeout = newTimeout;
    }
    
    public int getTimeout() {
        return timeout;
    }
}

3. Счётчики с одной записью:

public class Counter {
    private volatile long count = 0;
    
    public void increment() {
        count++;  // Не потокобезопасно!
    }
    
    public long getCount() {
        return count;
    }
}

// ВНИМАНИЕ: count++ не потокобезопасна!
// Это три операции: read, increment, write
// Для потокобезопасности используй AtomicLong

volatile НЕ гарантирует

1. Атомичность составных операций:

public class NotAtomic {
    private volatile int counter = 0;
    
    public void increment() {
        counter++;  // НЕ потокобезопасно!
        // Это read + write, не атомично
    }
}

2. Синхронизацию нескольких переменных:

public class NotSync {
    private volatile int x = 0;
    private volatile int y = 0;
    
    public void setValue(int newX, int newY) {
        x = newX;
        y = newY;  // Между этими двумя операциями другой поток может вклиниться
    }
}

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

1. Synchronized блок:

public class SynchronizedCounter {
    private int counter = 0;
    
    public synchronized void increment() {
        counter++;
    }
    
    public synchronized int getCount() {
        return counter;
    }
}

2. Atomic классы:

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicCounter {
    private AtomicInteger counter = new AtomicInteger(0);
    
    public void increment() {
        counter.incrementAndGet();
    }
    
    public int getCount() {
        return counter.get();
    }
}

3. ReentrantLock:

import java.util.concurrent.locks.ReentrantLock;

public class LockedCounter {
    private int counter = 0;
    private ReentrantLock lock = new ReentrantLock();
    
    public void increment() {
        lock.lock();
        try {
            counter++;
        } finally {
            lock.unlock();
        }
    }
}

Сравнение volatile vs synchronized vs Atomic

АспектvolatilesynchronizedAtomic
ВидимостьДаДаДа
АтомичностьНет*ДаДа
ПроизводительностьВысокаяСредняяВысокая
БлокировкаНетДаНет
Композитные операцииНетДаДа

*volatile обеспечивает видимость отдельных читаемых/записываемых операций.

Реальный пример: DoubleCheckedLocking

public class Singleton {
    private static volatile Singleton instance;  // volatile обязателен!
    
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

// Без volatile этот паттерн не работает правильно

Ключевые моменты

  • volatile обеспечивает видимость изменений между потоками
  • Это не замена для синхронизации сложных операций
  • Используй для простых флагов и полей, читаемых часто
  • Для атомичных операций предпочитай Atomic классы
  • Неправильное использование volatile может привести к тонким ошибкам, которые сложно отладить
Для чего нужен volatile? | PrepBro