Вопрос проверяет понимание ключевого слова volatile в Java и его влияния на видимость изменений примитивных переменных между потоками.
Ключевое слово volatile в Java применяется к переменным и решает две основные проблемы в многопоточном программировании: видимость изменений и упорядочивание операций. Для примитивных типов (например, int, boolean) его использование имеет специфические особенности.
Без volatile потоки могут кэшировать значения переменных в своей локальной памяти (например, в регистрах CPU или кэше ядра), и изменения, сделанные одним потоком, могут не сразу (или никогда) стать видимыми для других потоков. Объявление переменной как volatile гарантирует, что все операции чтения будут получать самое последнее записанное значение из основной памяти (RAM), а все операции записи будут немедленно сбрасываться в основную память. Это создаёт отношение happens-before между записью в volatile-переменную в одном потоке и последующим чтением этой же переменной в другом.
Для большинства примитивных типов (кроме long и double) операции чтения и записи и так являются атомарными. volatile не добавляет атомарности для составных операций, таких как инкремент (i++), который состоит из чтения, изменения и записи. Для таких операций требуется синхронизация (например, synchronized блок) или атомарные классы из java.util.concurrent.atomic.
Типичный сценарий — использование флага для остановки потока.
public class WorkerThread extends Thread {
private volatile boolean running = true;
public void run() {
while (running) {
// Выполняем работу
}
}
public void stopGracefully() {
running = false; // Изменение видно потоку run()
}
}Без volatile поток run() может никогда не увидеть изменение флага running на false, сделанное в методе stopGracefully(), и продолжит выполняться бесконечно.
volatile также накладывает ограничения на переупорядочивание операций компилятором и процессором. Операции чтения/записи volatile-переменной не могут быть переставлены с другими операциями чтения/записи памяти, что помогает избежать тонких ошибок в многопоточном коде.
Вывод: Используйте volatile для примитивных переменных, когда нужно обеспечить простую видимость их изменений между потоками, и операции над ними атомарны по своей природе (например, установка флага). Для сложных операций, требующих атомарности чтения-изменения-записи, выбирайте другие механизмы синхронизации.