Ответ
Оба ключевых слова связаны с многопоточностью, но решают разные проблемы и не являются взаимозаменяемыми.
volatile (Только видимость)
- Гарантирует видимость изменений переменной между потоками.
- Не гарантирует атомарность составных операций (например,
count++, который состоит из чтения, изменения и записи). - Механизм: Запрещает процессору и JVM кэшировать значение переменной в регистрах потока. Все чтения и записи идут напрямую в основную память (main memory).
- Использование: Для простых флагов или переменных состояния, запись в которые выполняется одним атомарным действием.
public class TaskController {
private volatile boolean isRunning = true; // Флаг виден всем потокам сразу
public void stop() {
isRunning = false; // Изменение сразу станет видно потоку worker
}
public void run() {
while (isRunning) { // Чтение всегда актуального значения из памяти
// выполнять работу
}
}
}
synchronized (Видимость + Атомарность + Взаимное исключение)
- Гарантирует взаимное исключение (mutual exclusion): только один поток может выполнять синхронизированный блок/метод для данного объекта-монитора.
- Гарантирует атомарность: Весь блок кода выполняется как одна неделимая операция.
- Гарантирует видимость: При выходе из synchronized-блока все изменения переменных, сделанные в нем, становятся видимыми другим потокам (запись в основную память).
- Использование: Для защиты критических секций, где состояние объекта может быть изменено несколькими потоками.
public class Counter {
private int count = 0;
// Без synchronized операция count++ не атомарна
public synchronized void increment() {
count++; // Чтение-изменение-запись защищены
}
public synchronized int getCount() {
return count; // Чтение также защищено для согласованности
}
}
Сравнительная таблица
| Критерий | volatile |
synchronized |
|---|---|---|
| Применимость | Только к переменным | К блокам кода и методам |
| Атомарность | Нет (только для одиночных операций записи) | Да |
| Видимость | Да (чтение/запись в основную память) | Да (вход/выход из блока) |
| Взаимное исключение | Нет | Да |
| Блокировка потоков | Нет | Да (может привести к ожиданию) |
| Производительность | Высокая | Ниже из-за накладных расходов на блокировку |
Правило выбора:
- Используйте
volatileдля простых флагов или переменных, записываемых одним потоком и читаемых многими, где атомарность не требуется. - Используйте
synchronized(или более современныеjava.util.concurrentпримитивы) для операций, требующих атомарности или изменения нескольких связанных переменных.