Является ли lazy потокобезопасным

Ответ

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, либо готовься к тому, что в один прекрасный день всё накроется медным тазом. Сам от себя охуеешь, когда начнёшь искать, почему данные кривые.