Исправление double-checked locking инициализации

Едва ли не каждый первый учебный пример по Room который можно найти в интернете содержит какую либо вариацию этого кода:

@Database(entities = [Item::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
    abstract fun itemDao(): ItemDao

    companion object {
        @Volatile
        private var INSTANCE: AppDatabase? = null

        fun getDatabase(context: Context): AppDatabase {
            return INSTANCE ?: synchronized(this) {
                Room.databaseBuilder(
                    context,
                    AppDatabase::class.java,
                    "database.db"
                ).fallbackToDestructiveMigration().build().also {
                    INSTANCE = it
                }
            }
        }
    }
}

И у этого решения есть проблема - здесь для инициализации базы данных используется double-checked locking (все то что внутри companion object) который невозможно безопасно реализовать на Java/Kotlin даже с Volatile. Это решение просто не работает.

Как правильно потокобезопасно (и желательно эффективно) инициализировать базу данных в данном примере?


Ответы (1 шт):

Автор решения: DedMoroz

Похоже что наиболее правильным решением в данном случае будет использование делегата by lazy который гарантирует потокобезопасность и однократность инициализации "из коробки" плюс он является ленивым. А саму ссылку на базу хранить в классе Application, который является синглтоном уровня приложения и доступен на протяжении всего жизненного цикла приложения и из любого компонента.

class BaseApplication : Application() {
    val appDatabase:AppDatabase by lazy {
        Room.databaseBuilder(
            applicationContext,
            AppDatabase::class.java,
            "database.db"
        ).fallbackToDestructiveMigration().build()
    }
}

Получение ссылки на базу:

val application = [email protected] //из активити
val application = [email protected]?.applicationContext //из фрагмента
val database = (application as? BaseApplication)?.appDatabase
→ Ссылка