В чём разница между volatile и synchronized?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Разница между 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
| Характеристика | volatile | synchronized |
|---|---|---|
| Видимость | ✓ Да | ✓ Да |
| Взаимное исключение | ✗ Нет | ✓ Да |
| Атомарность операций | Только чтение/запись | Группа операций |
| Производительность | Очень быстро | Медленнее |
| Использование памяти | Минимальное | Требуется монитор |
| 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)