Когда нужен Volatile

Ответ

Volatile нужен, когда переменная используется несколькими потоками, и важно, чтобы изменения были сразу видны всем потокам. Он гарантирует видимость изменений, но не атомарность операций.

Пример:

@Volatile
var isRunning = true

fun startWork() {
    thread {
        while (isRunning) { /* работа */ }
    }
}

fun stopWork() {
    isRunning = false
}

Без @Volatile поток может закешировать значение isRunning и не увидеть его изменение. Однако для сложных операций (например, инкремент) лучше использовать Atomic* или синхронизацию.

Ответ 18+ 🔞

Давай разберёмся, что за зверь такой этот @Volatile. Представь, что у тебя есть общая переменная, как последняя бутылка пива на троих. Без volatile один чувак её выпил, а остальные два ещё пялятся на полную, думая, что она на месте — вот это и есть проблема с кешированием потоков. @Volatile — это как крикнуть на всю квартиру: «Мужики, бутылка-то пустая уже, ёпта!». Все сразу состояние видят.

Вот смотри, классическая история:

@Volatile
var isRunning = true

fun startWork() {
    thread {
        while (isRunning) { /* работа */ }
    }
}

fun stopWork() {
    isRunning = false
}

Без этой аннотации поток в startWork() может закешировать значение isRunning у себя в регистрах или локальной памяти и тупо никогда не увидеть, что ты в другом потоке выставил isRunning = false. Он будет крутиться в цикле, пока система его не прибьёт, а ты будешь чесать репу, думая «какого хуя оно не останавливается?». С @Volatile изменение прямиком летит в основную память, и все потоки этот флаг видят сразу — доверия к нему, блядь, полный.

Но! И это важно, как мать родная. Volatile гарантирует только видимость изменений, а не атомарность операций. Объясняю на пальцах: он крикнет всем, что значение поменялось, но если два потока пытаются сделать counter++ (прочитать-увеличить-записать), то тут будет полный пиздец и гонка. Оба прочитают, скажем, 5, оба увеличат до 6, и запишут 6. А должно было быть 7. Понимаешь? Овердохуища потерянных инкрементов.

Поэтому для счётчиков и прочей хитрой жопы, где важна неделимость операции, надо брать тяжёлую артиллерию: либо AtomicInteger, AtomicBoolean и прочие Atomic* (они, сука, умные и атомарные внутри), либо классическую синхронизацию через synchronized или мьютексы. Volatile — это для простых флагов и сигналов остановки, где операция записи или чтения одна. Всё, вопрос закрыт.