Что означает ключевое слово volatile в Android (Java/Kotlin)?

«Что означает ключевое слово volatile в Android (Java/Kotlin)?» — вопрос из категории Other, который задают на 10% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

volatile — это ключевое слово в Java (и аннотация @Volatile в Kotlin), которое гарантирует видимость изменений переменной для всех потоков. Оно указывает, что переменная может быть изменена одновременно несколькими потоками, и ее значение не должно кэшироваться в регистрах или локальной памяти потока.

Решаемая проблема: Без volatile один поток может изменить переменную в своей локальной кэш-памяти, а другой поток продолжит читать устаревшее значение из основной памяти или своего кэша.

Пример (Kotlin):

class WorkerTask {
    @Volatile // Аннотация в Kotlin
    var isRunning = true

    fun startBackgroundWork() {
        thread(name = "WorkerThread") {
            // Фоновый поток читает isRunning из основной памяти
            while (isRunning) {
                // Выполняем полезную работу
                Thread.sleep(100)
            }
            println("Фоновый поток завершился.")
        }
    }

    fun stopWork() {
        // Главный поток записывает новое значение в основную память
        isRunning = false
        println("Флаг остановки установлен.")
    }
}

// Использование
val task = WorkerTask()
task.startBackgroundWork()
Thread.sleep(1000)
task.stopWork() // Без @Volatile фоновый поток может не увидеть изменение isRunning

Что гарантирует volatile:

  1. Видимость (Visibility): Запись в volatile-переменную происходит напрямую в основную память, а чтение — из основной памяти. Это делает изменение немедленно видимым для всех других потоков.
  2. Запрет переупорядочивания (Ordering): Операции чтения/записи volatile-переменной не могут быть переупорядочены компилятором или процессором относительно других операций с памятью, что создает частичный барьер памяти.

Чего НЕ гарантирует volatile:

  • Атомарность (Atomicity): volatile не делает составные операции (например, инкремент counter++) атомарными. Такая операция состоит из чтения, изменения и записи, и другой поток может вмешаться между этими шагами.

Когда использовать:

  • Для простых флагов состояния (isRunning, isInitialized).
  • Когда только один поток пишет, а многие читают.

Альтернативы для сложных случаев:

  • Атомарные операции: Используйте классы из java.util.concurrent.atomic (например, AtomicInteger).
  • Синхронизация: Используйте synchronized блоки или Lock.
  • Потокобезопасные коллекции: ConcurrentHashMap и другие.
  • Корутины Kotlin: Используйте Mutex или потокобезопасные примитивы из kotlinx.coroutines.