Является ли lazy потокобезопасным?
Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Потокобезопасность 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)
- Тестовые среды или демонстрационные проекты
- Контексты, где ты полностью контролируете доступ к свойству
Важные исключения и граничные случая
-
Проблема повторной инициализации: Даже в SYNCHRONIZED режиме, если инициализатор выбрасывает исключение при первом вызове, он может быть вызван повторно.
-
Изменение состояния после инициализации:
lazyгарантирует только потокобезопасность инициализации, но не защищает от последующих изменений объекта. Если lazy-объект mutable, нужна дополнительная синхронизация. -
Android специфика: В Android контексте часто безопасно использовать
lazyбез явного указания режима (SYNCHRONIZED по умолчанию) для инициализации компонентов вActivityилиFragment, поскольку эти методы обычно вызываются из главного потока. Однако для глобальных объектов (в Application классе или repository) всегда рекомендуется SYNCHRONIZED.
Итог
- По умолчанию
lazyпотокобезопасен (использует LazyThreadSafetyMode.SYNCHRONIZED) - Потокобезопасность можно контролировать через явное указание режима
- Для многопоточных Android приложений рекомендуется использовать режим SYNCHRONIZED для критичных ресурсов
- Режим NONE следует избегать в многопоточном контексте, кроме специальных случаев с гарантированным однопоточным доступом
Таким образом, ответ на исходный вопрос: Да, lazy является потокобезопасным по умолчанию, но это поведение можно изменить выбором другого режима синхронизации.