Ответ
Оба ключевых слова связаны с многопоточностью, но решают разные проблемы и не являются взаимозаменяемыми.
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примитивы) для операций, требующих атомарности или изменения нескольких связанных переменных.
Ответ 18+ 🔞
Давай разберём эту дичь, а то народ путает как слепой кот в сортире. volatile и synchronized — это как молоток и отвёртка: оба в ящике с инструментами, но ты же не будешь гвозди отвёрткой забивать, верно? Хотя некоторые пытаются, блядь.
volatile — это про видимость, ёпта!
Представь, у тебя в кеше процессора лежит переменная, и каждый поток тащит свою копию, как собака кость. И один поток её поменял, а остальные об этом нихуя не знают — сидят, старые значения жуют. Так вот volatile — это такой крик на всю деревню: «Эй, мудаки, смотрите в основную память!». Он запрещает кеширование. Записал значение — оно сразу для всех видно. Но, внимание, хуйня в том, что атомарности тут нет! Операция i++ для volatile-переменной — это всё равно три шага: прочитать, увеличить, записать. И если два потока в это время впендюрятся, будет classic race condition, пиздец и слёзы.
public class TaskController {
private volatile boolean isRunning = true; // Флаг. Один поток пишет, другие читают.
public void stop() {
isRunning = false; // Написал — и всем сразу видно. Идеально.
}
public void run() {
while (isRunning) { // Читает всегда свежее из памяти, а не свою устаревшую хуйню.
// делаем дела
}
}
}
synchronized — это тяжёлая артиллерия, блядь. Тут уже не просто видимость, а полный контроль. Это как зайти в сортир и закрыться на ключ. Пока ты там делаешь свои дела, все остальные ждут у двери, терпя бесконечное говно. Гарантирует и видимость, и атомарность, и взаимное исключение (mutual exclusion, если по-пацански).
public class Counter {
private int count = 0; // Даже не volatile, ибо synchronized своё дело сделает.
public synchronized void increment() {
count++; // Всё! Ни один другой поток сюда не сунется, пока этот не закончит.
}
public synchronized int getCount() {
return count; // И читать тоже через synchronized, чтобы гарантированно свежее взять.
}
}
Краткая памятка для тех, кто в танке:
| Что сравниваем | volatile |
synchronized |
|---|---|---|
| Суть | «Смотрите в основную память!» | «Не лезь, пока я не выйду!» |
| Атомарность | Нихуя. count++ разъебут. |
Да. Всё внутри блока — единая операция. |
| Видимость | Да. Запись сразу видна всем. | Да. При выходе из блока всё сбрасывается в память. |
| Блокировка | Нет. Потоки не останавливаются. | Да. Остальные ждут, как лохи. |
| Производительность | Быстро, почти без накладок. | Медленнее, потому что управление блокировками. |
Итог, ебать его в сраку:
- Висит простой флажок
boolean isDone?volatileв самый раз. - Нужно сделать
i++, или изменить несколько переменных согласованно?synchronized(или посмотри в сторонуAtomicInteger, это вообще отдельная песня). - Использовать
volatileдля атомарных операций — это как пытаться забить гвоздь тапком. Вроде и стучишь, но результат обычно пиздецовый.