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

В чём разница между volatile и synchronized?

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

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

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

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

Разница между volatile и synchronized

volative и synchronized — это два различных механизма для управления доступом к общим данным в многопоточной среде. Каждый из них решает разные проблемы и имеет разные характеристики производительности. Правильное использование этих инструментов критично для написания корректного многопоточного кода.

volatile: гарантии видимости

volatile — это модификатор переменной, который гарантирует видимость изменений между потоками. Значение volatile переменной всегда читается непосредственно из основной памяти и записывается туда.

Ключевые характеристики volatile:

  • Видимость: Изменения видны всем потокам немедленно
  • Без блокировки: Не использует блокировки, очень быстро
  • Невозможна синхронизация: Несколько операций не могут быть атомарными
  • Для простых значений: Лучше всего подходит для простых типов данных
public class VolatileExample {
    // Флаг завершения потока
    private volatile boolean running = true;
    private volatile int counter = 0;
    
    public void stopThread() {
        running = false; // Это изменение видно всем потокам
    }
    
    public void startWorker() {
        new Thread(() -> {
            while (running) {
                counter++; // Увеличение счётчика
                System.out.println("Работаю: " + counter);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("Поток остановлен");
        }).start();
    }
    
    public static void main(String[] args) throws InterruptedException {
        VolatileExample example = new VolatileExample();
        example.startWorker();
        
        Thread.sleep(500);
        example.stopThread(); // Флаг будет видим потоку-рабочему
    }
}

Проблема без volatile: видимость памяти

public class VisibilityProblem {
    private boolean ready = false; // БЕЗ volatile
    private int value = 0;
    
    public void thread1Work() {
        new Thread(() -> {
            value = 42;
            ready = true; // Изменения могут остаться в кэше CPU
        }).start();
    }
    
    public void thread2Work() {
        new Thread(() -> {
            while (!ready) {
                // Может бесконечно ждать, так как ready может остаться в кэше
                Thread.yield();
            }
            System.out.println("Value: " + value); // Может быть 0, а не 42
        }).start();
    }
}

synchronized: синхронизация и мьютекс

synchronized — это встроенный механизм для получения монитора (мьютекса) объекта. Он гарантирует как видимость, так и атомарность группы операций.

Ключевые характеристики synchronized:

  • Видимость: Гарантирует видимость как и volatile
  • Взаимное исключение: Только один поток может находиться в синхронизированном блоке
  • Атомарность: Сложные операции выполняются без прерывания
  • Производительность: Медленнее volatile, но проще для синхронизации
public class SynchronizedExample {
    private int balance = 1000;
    
    // Синхронизированный метод
    public synchronized void deposit(int amount) {
        int temp = balance;
        // Без synchronized другой поток может прочитать balance здесь
        temp += amount;
        // И записать старое значение
        balance = temp;
    }
    
    public synchronized void withdraw(int amount) {
        if (balance >= amount) {
            balance -= amount;
        }
    }
    
    public synchronized int getBalance() {
        return balance;
    }
    
    public static void main(String[] args) throws InterruptedException {
        SynchronizedExample account = new SynchronizedExample();
        
        // Несколько потоков делают операции
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                account.deposit(1);
            }
        });
        
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                account.withdraw(1);
            }
        });
        
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        
        System.out.println("Финальный баланс: " + account.getBalance());
    }
}

Синхронизированные блоки

public class SynchronizedBlockExample {
    private int counter = 0;
    private Object lock = new Object(); // Объект блокировки
    
    public void increment() {
        // Синхронизированный блок с явным объектом блокировки
        synchronized (lock) {
            counter++;
        }
        // Остальной код выполняется без блокировки
    }
    
    public int getCounter() {
        synchronized (lock) {
            return counter;
        }
    }
}

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

Характеристикаvolatilesynchronized
Видимость✓ Да✓ Да
Взаимное исключение✗ Нет✓ Да
Атомарность операцийТолько чтение/записьГруппа операций
ПроизводительностьОчень быстроМедленнее
Использование памятиМинимальноеТребуется монитор
Deadlock возможен✗ Нет✓ Да (при неправильном использовании)
Сложность кодаПростаяСредняя

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

volatile используется для:

public class SignallingExample {
    private volatile boolean shutdownRequested = false;
    
    public void run() {
        while (!shutdownRequested) {
            // Работа потока
        }
    }
    
    public void shutdown() {
        shutdownRequested = true; // Безопасно и быстро
    }
}

synchronized используется для:

public class BankAccount {
    private long balance = 0;
    
    public synchronized void transfer(long amount, BankAccount to) {
        if (this.balance >= amount) {
            this.balance -= amount; // Атомарная операция
            to.balance += amount;
        }
    }
}

AtomicInteger как альтернатива

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicExample {
    // AtomicInteger комбинирует volatile видимость с атомарными операциями
    private AtomicInteger counter = new AtomicInteger(0);
    
    public void increment() {
        counter.incrementAndGet(); // Атомарная операция
    }
    
    public int getCounter() {
        return counter.get();
    }
    
    public static void main(String[] args) throws InterruptedException {
        AtomicExample example = new AtomicExample();
        
        Thread[] threads = new Thread[10];
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    example.increment();
                }
            });
            threads[i].start();
        }
        
        for (Thread t : threads) {
            t.join();
        }
        
        System.out.println("Финальное значение: " + example.getCounter()); // 10000
    }
}

Правило выбора

Используй volatile когда:

  • Нужно просто уведомить потоки об изменении флага
  • Работаешь с простыми типами данных (boolean, int, long)
  • Производительность критична
  • Не нужна синхронизация нескольких операций

Используй synchronized когда:

  • Нужно защитить сложную логику от одновременного доступа
  • Требуется атомарность группы операций
  • Нужен монитор для wait/notify механизма

Используй Atomic когда:*

  • Нужны атомарные операции на простых типах
  • Производительность важнее, чем synchronized
  • Java 5+ (доступен java.util.concurrent.atomic)