Ответ
lazy в Kotlin по умолчанию не потокобезопасен. Если инициализация происходит в нескольких потоках одновременно, может возникнуть race condition, и лямбда выполнится несколько раз.
Для потокобезопасной ленивой инициализации используйте LazyThreadSafetyMode:
val safeLazyValue: String by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
"Thread-safe value"
}
Доступные режимы:
SYNCHRONIZED(по умолчанию в Android) — блокирует все потоки, кроме одного.PUBLICATION— допускает множественную инициализацию, но возвращает первый результат.NONE— небезопасный, но самый быстрый (только для однопоточного использования).
Пример без синхронизации (опасно!):
val unsafeLazyValue by lazy { ... } // Может сломаться в мультипоточной среде Ответ 18+ 🔞
Да ты посмотри, какая хитрая жопа эта ленивая инициализация! Всё вроде красиво, by lazy, и значение само появится, когда спросишь. Ан нет, ёпта, подвох есть.
Вот объявляешь ты переменную, как будто всё окей:
val unsafeLazyValue by lazy { ... }
И думаешь: "Ну, чувак, красота, сейчас оно само всё посчитает". А потом запускаешь это дело в нескольких потоках, и начинается ёперный театр. Потоки-то могут налететь на эту лямбду одновременно, как гомосеки на бутерброд! И что выходит? А выходит, что твоя инициализирующая функция выполнится не один раз, а несколько, и последний результат перезапишет предыдущий. Или вообще всё упадёт. Короче, доверия ебать ноль к такому подходу в многопоточной среде.
Так что же делать, спросишь ты? А надо просто режим безопасности указать, блядь! Вот смотри:
val safeLazyValue: String by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
"Thread-safe value"
}
Вот это уже дело. Тут уже можно спать спокойно. А режимов этих есть несколько, на выбор:
SYNCHRONIZED— Это как дежурный у турникета. Он всех остальных потоков нахуй пошлёт и впустит только один, чтобы тот всё инициализировал. Потом все получат готовый результат. В Android, кстати, это режим по умолчанию, молодцы.PUBLICATION— Немного другой прикол. Он может допустить, чтобы несколько потоков начали инициализацию, но всем раздаст результат от первого, кто успел. Остальные вычисления просто выкинет нахуй. Как бы компромиссный вариант.NONE— А вот это, блядь, полный распиздяй. Никаких блокировок, никакой синхронизации. Быстрее всех, конечно. Но использовать можно только если ты на 100500% уверен, что обращаться к этой переменной будет один-единственный поток. Иначе будет тебе хиросима, а не программа.
Так что запомни, lazy по дефолту — не потокобезопасен (кроме как в Android). И если пишешь что-то для общего доступа, либо явно указывай SYNCHRONIZED, либо готовься к тому, что в один прекрасный день всё накроется медным тазом. Сам от себя охуеешь, когда начнёшь искать, почему данные кривые.