在多线程应用程序Android中访问数据库的最佳方法?

the*_*ger 10 java sqlite multithreading android

注意:请不要将此问题标记为重复.我已经经历了几个类似的问题,但找不到满意的答案.

我一直在研究使用Sqlite数据库的应用程序.我们遵循单例模式,确保我们只能在整个应用程序中创建一个辅助类实例.

public class CustomSqliteHelper extends SQLiteOpenHelper {

  public static CustomSqliteHelper getInstance(Context context) {
    if (instance == null) {
      synchronized (CustomSqliteHelper.class) {
        if (instance == null) {
          instance = new CustomSqliteHelper(context);
        }
      }
    }
    return instance;
  }
}
Run Code Online (Sandbox Code Playgroud)

但有时应用程序崩溃了SQLiteDatabaseLockedException.我理解当多个线程/进程尝试一次写入数据库时​​会出现此异常.即使一个线程/进程在写操作仍在进行时尝试读取数据库,也会抛出此异常.

所以我一直在阅读很多关于这个以及防止这种情况发生的可能方法.很多帖子建议使用ContentProvider而不是直接扩展SqliteOpenHelper类和对数据库对象执行操作.在阅读其中一篇帖子时,这篇文章提到在使用Content Provider时,您不需要手动处理多线程环境.

虽然ContentProvider缺乏线程安全性,但通常您会发现在防止潜在的竞争条件方面您无需采取进一步行动.规范示例是您的ContentProvider由SQLiteDatabase支持的时间; 当两个线程同时尝试写入数据库时​​,SQLiteDatabase将自行锁定,确保一个等待直到另一个完成.每个线程都将获得对数据源的互斥访问,从而确保满足线程安全性.

以上引用似乎令人困惑,因为首先它提到ContentProvider不支持线程安全.但他总结说,应用程序开发人员不需要做任何事情来实现并发.

另外,如果我选择使用SqliteOpenHelper,那么防止这些崩溃的最佳方法是什么?我一直在考虑为每个db操作使用锁.

 public class CustomSqliteHelper extends SQLiteOpenHelper {

 private String lock = "lock";

 public void insert(){
    synchronized(lock){
       // Do the insert operation here.
    }
 }


 public void update(){
    synchronized(lock){
       // Do the update operation here.
    }
 }
 }
Run Code Online (Sandbox Code Playgroud)

但我的一个团队成员建议我不要这样做,因为Java锁很贵.

在经历了Github上最受欢迎的项目之一后,我发现开发人员建议将每个数据库操作包装在一个事务中.

public void insert(ContentValues values) {
    // Create and/or open the database for writing
    SQLiteDatabase db = getWritableDatabase();

    // It's a good idea to wrap our insert in a transaction. This helps with performance and ensures
    // consistency of the database.
    db.beginTransaction();
    try {
        // The user might already exist in the database (i.e. the same user created multiple posts).

        db.insertOrThrow(TABLE_POSTS, null, values);
        db.setTransactionSuccessful();
    } catch (Exception e) {
        Log.d(TAG, "Error while trying to add post to database");
    } finally {
        db.endTransaction();
    }
}
Run Code Online (Sandbox Code Playgroud)

我真的不确定这是否可以阻止Lock异常?这似乎更像是一个以绩效为导向的步骤.

所以在阅读完所有博客和教程之后,我仍然感到困惑.我的主要问题是

  1. 鉴于我的应用程序不与其他应用程序共享数据,使用ContentProviders或扩展SqliteOpenHelper是更好的选择.
  2. 将Java锁定放在所有操作上是最好的方法还是有比这更好的其他方法?

更新:根据@Mustanar的回答,似乎SQLiteDatabase负责锁定机制.这意味着如果您正在执行写操作,则数据库将被锁定.但同时,如果某个其他线程尝试执行写操作,那么第二个操作是否会等待直到锁被释放或者是否会抛出android.sqlite.database.SQLiteDatabaseLockedException异常?

更新2:对这个问题给予赏金,因为答案似乎仍然不清楚.我只使用Helper类的一个实例.但仍然得到这个错误.

PS:感谢您提出这么长的问题.

Mus*_*eed 5

使用Singleton模式来实例化SQLiteOpenHelper,因此在整个应用程序中应该存在一个单例实例。这将确保不会发生泄漏,并使您的生活更轻松,因为它消除了在编写代码时忘记关闭数据库的可能性。它还将确保在整个应用程序中安全访问数据库。

此外,您不必实现自己的锁定机制。SQLiteDatabase保持锁定机制。因此,在特定时间只有一个事务在进行,所有其他事务都将Queue使用该Sigleton.getInstance()方法。确保数据库的单一访问点。

此外,在这种方法中,您不必关闭数据库连接,因为一旦事务完成,SQLiteDatabase 将释放所有引用。SoCustomSQLiteHelper.getInstance()应该在您想要执行 CRUD 操作并删除锁定 mecansim 时使用。

更多信息,请浏览博客http://www.androiddesignpatterns.com/2012/05/correctly-managing-your-sqlite-database.html并查看评论。

希望这可以帮助。