← Назад к вопросам

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

1.8 Middle🔥 172 комментариев
#Kotlin основы#Многопоточность и асинхронность

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Потокобезопасность lazy инициализации в Kotlin

Вопрос о потокобезопасности lazy инициализации в Kotlin является фундаментальным для понимания многопоточных приложений на Android и других платформах.

Основные режимы lazy

lazy в Kotlin — это функция делегирования свойств, которая выполняет инициализацию только при первом обращении к свойству. Потокобезопасность зависит от выбранного режима синхронизации, который передается как параметр:

val lazyValue1: String by lazy { // Без параметра - используется LazyThreadSafetyMode.SYNCHRONIZED
    computeValue()
}

val lazyValue2: String by lazy(LazyThreadSafetyMode.NONE) {
    computeValue() // НЕ потокобезопасно
}

Режимы потокобезопасности

Kotlin предоставляет три режима через enum LazyThreadSafetyMode:

1. SYNCHRONIZED (используется по умолчанию)

  • Полностью потокобезопасный режим
  • Использует мьютексы (mutex) для синхронизации
  • Гарантирует, что инициализатор будет вызван только один раз даже при одновременном обращении из нескольких потоков
  • Пример:
val safeLazyValue: Resource by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
    Resource.load() // Будет вызвано только один раз
}

2. PUBLICATION

  • Условно потокобезопасный режим
  • Инициализатор может быть вызван несколько раз в многопоточной среде, но возвращается только первый успешный результат
  • Использует более легковесные механизмы синхронизации (CAS операции)
  • Подходит для случаев, где инициализация недорогая и допустимы повторные вызовы
val publicationLazy: Config by lazy(LazyThreadSafetyMode.PUBLICATION) {
    Config.parse() // Может быть вызвано несколько раз параллельно
}

3. NONE

  • НЕ потокобезопасный
  • Не использует синхронизацию вообще
  • Инициализатор может быть вызван несколько раз в многопоточном контексте
  • Может привести к race conditions, неконсистентному состоянию или повторной инициализации
val unsafeLazy: Cache by lazy(LazyThreadSafetyMode.NONE) {
    Cache.create() // Опасно в многопоточном контексте
}

Практические рекомендации для Android

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

  • Инициализация дорогостоящих ресурсов (сетевые запросы, чтение больших файлов)
  • Singleton объекты, которые должны быть уникальными в приложении
  • Shared preferences, Room Database инстансы
  • ViewModels или другие компоненты жизненного цикла
// Пример в Android приложении
class MyRepository {
    val database: AppDatabase by lazy { // SYNCHRONIZED по умолчанию
        Room.databaseBuilder(context, AppDatabase::class.java, "app.db").build()
    }
}

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

  • Легковесные вычисления (математические операции, простые преобразования данных)
  • Ситуации, где повторная инициализация не приводит к критическим проблемам

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

  • Инициализация в одном потоке (например, только в Main/UI потоке Android)
  • Тестовые среды или демонстрационные проекты
  • Контексты, где ты полностью контролируете доступ к свойству

Важные исключения и граничные случая

  1. Проблема повторной инициализации: Даже в SYNCHRONIZED режиме, если инициализатор выбрасывает исключение при первом вызове, он может быть вызван повторно.

  2. Изменение состояния после инициализации: lazy гарантирует только потокобезопасность инициализации, но не защищает от последующих изменений объекта. Если lazy-объект mutable, нужна дополнительная синхронизация.

  3. Android специфика: В Android контексте часто безопасно использовать lazy без явного указания режима (SYNCHRONIZED по умолчанию) для инициализации компонентов в Activity или Fragment, поскольку эти методы обычно вызываются из главного потока. Однако для глобальных объектов (в Application классе или repository) всегда рекомендуется SYNCHRONIZED.

Итог

  • По умолчанию lazy потокобезопасен (использует LazyThreadSafetyMode.SYNCHRONIZED)
  • Потокобезопасность можно контролировать через явное указание режима
  • Для многопоточных Android приложений рекомендуется использовать режим SYNCHRONIZED для критичных ресурсов
  • Режим NONE следует избегать в многопоточном контексте, кроме специальных случаев с гарантированным однопоточным доступом

Таким образом, ответ на исходный вопрос: Да, lazy является потокобезопасным по умолчанию, но это поведение можно изменить выбором другого режима синхронизации.