【问题标题】:Singleton design pattern to get a RoomDatabase instance获取 RoomDatabase 实例的单例设计模式
【发布时间】:2021-08-31 15:47:02
【问题描述】:

我正在使用 RoomDatabase,在设置了我的 DAO、数据库和实体类之后,我需要找到一种方法来获取 RoomDatabase 实例,但在文档中,我看到了:

如果您的应用在单个进程中运行,则应遵循单例 实例化 AppDatabase 对象时的设计模式。每个 RoomDatabase 实例相当昂贵,您很少需要访问 到单个进程中的多个实例。

所以从Udacity 课程中我看到有多种方法可以做到这一点:第一个:

private lateinit var INSTANCE: MainDBForObjects
fun getDatabase(context: Context): MainDBForObjects{
    if (!::INSTANCE.isInitialized){
        INSTANCE = Room.databaseBuilder(
            context,
            MainDBForObjects::class.java, "database"
        ).fallbackToDestructiveMigration()
            .build()
    }
    return INSTANCE
}

另一个是来自数据库抽象类的伴随对象:

 companion object {

        @Volatile
        private var INSTANCE: SleepDatabase? = null

        fun getInstance(context: Context): SleepDatabase {

            synchronized(this) {

                var instance = INSTANCE

                if (instance == null) {
                    instance = Room.databaseBuilder(
                            context.applicationContext,
                            SleepDatabase::class.java,
                            "sleep_history_database"
                    )
         
                            .fallbackToDestructiveMigration()
                            .build()
             
                    INSTANCE = instance
                }
                return instance
            }
        }
    }

那么这两者之间有什么重要区别吗?是不是一个比另一个好?

【问题讨论】:

  • 第一种方法不是线程安全的,第二种方法也是双重检查锁定的错误实现,因此尽管它是线程安全的,但即使实例是线程安全的,也会产生更高的同步成本(获取锁)已经初始化了
  • 您能否将其添加为答案以及正确的实现?

标签: android singleton android-room


【解决方案1】:

添加了通过双重检查锁定创建单例对象的实现。

这将确保仅在需要时获取锁(如果数据库对象未初始化),因为同步通常很昂贵

这里的双重检查是指在synchronized块内再次进行空检查,这有助于我们在thread A第一次获得锁来初始化对象并且其他线程也在等待它的情况下@ 987654324@ 现在只要thread A 初始化对象并释放锁,所有等待的线程立即获得更新的值。

需要使用局部变量是为了确保部分初始化的对象对线程不可见,导致状态不一致(与语言语义有关,更多信息在这里 --> https://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java

companion object {

    @Volatile
    private var sInstance: SleepDatabase? = null

    @JvmStatic
    fun getInstance(context: Context): SleepDatabase {
        val localInstance = sInstance

        if (localInstance != null) {
            return localInstance
        }

        return synchronized(this) {
            var instance = sInstance
            if (instance == null) {
                instance = Room.databaseBuilder(
                                 context.applicationContext,
                                 SleepDatabase::class.java,
                                 "sleep_history_database"
                               )
                               .fallbackToDestructiveMigration()
                               .build()
                 sInstance = instance
            }

            instance
       }
    }
}

【讨论】:

  • 我收到此错误“智能转换为 'MainDBForObjects' 是不可能的,因为 'sInstance' 是一个可变属性,此时可能已更改”
  • 是的,这是一个 lint 检查,我们可以通过引入一个局部变量来解决。在我们的示例中,我们可以确定 sInstance 在我们的方法 getInstance 之外没有更改,但静态分析无法理解它并抱怨,因为它看到了一个可能为 null 的可变属性
猜你喜欢
  • 1970-01-01
  • 2011-08-09
  • 2017-10-12
  • 1970-01-01
  • 2010-11-03
相关资源
最近更新 更多