В каких режимах может работать by lazy

Ответ

by lazy в Kotlin может работать в трёх режимах:

  1. LazyThreadSafetyMode.SYNCHRONIZED (по умолчанию)
    Гарантирует потокобезопасность, но с небольшими накладными расходами на синхронизацию:
    val lazyValue: String by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
        "Initialized"
    }
  1. LazyThreadSafetyMode.PUBLICATION
    Разрешает множественную инициализацию, но возвращает первый успешный результат:
    val lazyValue by lazy(LazyThreadSafetyMode.PUBLICATION) {
        Thread.sleep(100)
        "Initialized"
    }
  1. LazyThreadSafetyMode.NONE
    Не потокобезопасен, но самый быстрый. Используется только в однопоточных сценариях:
    val lazyValue by lazy(LazyThreadSafetyMode.NONE) {
        "Initialized"
    }

Ответ 18+ 🔞

Э, слушай, давай разберем эту штуку by lazy в Kotlin, а то народ иногда путается, как будто на дворе 2002-й год и все синхронизируют вручную. Ситуация пиздец простая, но есть нюансы, как говорится.

Вот представь: у тебя есть тяжелый объект, который создавать — ебушки-воробушки, ресурсов жрёт, как не в себя. Инициализировать его сразу — хуй с горы, потому что он может вообще не понадобиться. Вот для этого и придумали ленивую инициализацию. by lazy — это как раз она, сука. Объект создается только тогда, когда к нему впервые обращаются. А дальше уже хранится готовый. Красота.

Но, как всегда, есть манда с ушами в виде многопоточности. Если твой код могут дергать из нескольких потоков одновременно, нужно решить, как эту инициализацию синхронизировать, чтобы не получить овердохуища копий объекта или гонок. Для этого есть три режима, и они задаются вот этим LazyThreadSafetyMode.

Первый режим — SYNCHRONIZED (он же по умолчанию). Это самый надежный, но и не самый шустрый парень. Он как охранник в клубе: пропускает только одного внутрь (в блок инициализации), остальные ждут снаружи, пока тот не выйдет с готовым значением. Гарантирует, что инициализация будет ровно одна, даже если на объект набросится толпа потоков. Накладные расходы на синхронизацию есть, но обычно они да похуй, если инициализация сама по себе дорогая.

val тяжелыйОбъект: String by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
    println("Ёпта, инициализируюсь, всем стоять!")
    "Готово"
}

Второй режим — PUBLICATION. Вот это уже хитрая жопа. Он разрешает нескольким потокам зайти в блок инициализации одновременно. Все они начинают вычислять значение. Но! Победит тот, кто первый успешно закончит и запишет результат. Остальные вычисления просто отбрасываются. Это может быть полезно, если сама инициализация не имеет побочных эффектов и можно позволить потокам потратить ресурсы впустую, зато первый получит ответ быстрее. Но если у тебя там, например, подключение к базе открывается, то это пидарас шерстяной подход.

val lazyValue by lazy(LazyThreadSafetyMode.PUBLICATION) {
    // Сюда могут зайти несколько потоков
    Thread.sleep(100)
    "Победил самый быстрый поток"
}

Третий режим — NONE. А это, блядь, полупидор режим. Вообще никакой синхронизации. Доверия ебать ноль. Если два потока одновременно обратятся к нему в первый раз, можно получить две разных копии объекта, гонки данных и прочий распиздяй. Используется ТОЛЬКО когда ты на 100500% уверен, что обращение будет из одного потока. Зато он самый быстрый, потому что никаких замков. В UI-потоке на Android, например, иногда можно юзать, но осторожно, вы ходите по охуенно тонкому льду.

// Только для главного потока! Иначе будет тебе хиросима.
val uiOnlyValue by lazy(LazyThreadSafetyMode.NONE) {
    "Быстро и без замков"
}

Короче, суть в чем:

  • Нужна надежность из любого потока — бери SYNCHRONIZED (или просто by lazy, это одно и то же). Волнение ебать на ноль.
  • Инициализация безопасная, хочется чуть скорости — глянь в сторону PUBLICATION.
  • Работаешь в одном потоке и терпения ноль ебать на накладные расходы — NONE, но помни, что за косяки вилкой в глаз.

Вот и вся магия. Выбирай с умом, а не тыкай наугад.