7

sqlite データベースへのマルチスレッド アクセスで失敗しないパターンを取得しようとしています。また、問題を再現できないことも私を悩ませています。

DB を使用するアプリがありますが、アプリのデータを同期するために Android アカウントと Android 同期も使用します。私の推測では、2 つが同時に発生すると、クラッシュします。次のような多くのエラーが発生します。

 * android.database.sqlite.SQLiteDatabaseLockedException: database is locked
 * android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)
 * android.database.sqlite.SQLiteDatabaseLockedException: error code 5: database is locked
 * android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5): , while compiling: PRAGMA journal_mode
 * android.database.sqlite.SQLiteDiskIOException: disk I/O error (code 778)
 * android.database.sqlite.SQLiteException: Failed to change locale for db '/data/data/net.bicou.redmine/databases/redmine.db' to 'en_US'. \n Caused by: android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)

それらのすべてが同じ根本原因に関連しているわけではないかもしれませんが、私はちょっと迷っています.

私が持っているものは次のとおりです。

  • DbAdapter単一のテーブルを管理するサブクラスによって拡張される抽象基本クラス
  • と呼ばれる SQLite データベースを管理するクラスDbManagerLock

現在、ユーザーはDbManagerシングルトンではない のバージョンを使用しています。DbManagerすべてのスレッドが同じオブジェクトを共有するように、シングルトンを作成する予定です。私が理解している/見た限りでは、バックグラウンド同期とアプリは同じプロセスを共有しているため、これは問題にはなりません。

クラスは次のとおりです (関連する部分のみ)。

public abstract class DbAdapter {
    Context mContext;
    protected DbManager mDbManager;
    SQLiteDatabase mDb;

    public static final String KEY_ROWID = "_id";

    public DbAdapter(final Context ctx) {
        mContext = ctx;
    }

    public DbAdapter(final DbAdapter other) {
        mContext = other.mContext;
        mDb = other.mDb;
        mDbManager = other.mDbManager; // removed with singleton version
    }

    public synchronized DbAdapter open() throws SQLException {
        if (mDb != null) {
            return this;
        }

        mDbManager = new DbManager(mContext); // currently in production
        mDbManager = DbManager.instance(mContext); // currently investigating this singleton solution
        try {
            mDb = mDbManager.getWritableDatabase();
        } catch (final SQLException e) {
            L.e("Unable to open DB, trying again in 1 second", e);
            try {
                Thread.sleep(1000);
            } catch (final InterruptedException e1) {
                L.e("Could not wait 1 second " + e1);
            }
            mDb = mDbManager.getWritableDatabase();// This may crash
        }

        return this;
    }

    public synchronized void close() {
        mDbManager.close();
        mDbManager = null;
        mDb = null;
    }
}

データベース テーブルを処理する必要があるクラスは、 を拡張し、、、 などDbAdapterのメソッドを実装します。selectinsertdelete

DBマネージャーは次のとおりです。

public class DbManager extends SQLiteOpenHelper {
    private static final String DB_FILE = "db";
    private static final int DB_VERSION = 15;
    Context mContext;
    Lock mLock = new ReentrantLock();

    // Currently in prod
    public DbManager(final Context context) {
        super(context, DB_FILE, null, DB_VERSION);
        mContext = context;
    }

    // singleton version will make this constructor private and add:
    private static DbManager mInstance;
    public static synchronized DbManager instance(Context context) {
        if (instance == null) {
            instance = new DbManager(context);
        }
        return instance;
    }

    @Override
    public SQLiteDatabase getWritableDatabase() {
        mLock.lock();
        return super.getWritableDatabase();
    }

    @Override
    public void close() {
        super.close();
        mLock.unlock();
    }

    @Override
    public void onCreate(final SQLiteDatabase db) {
        // ...
    }

    @Override
    public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {
        // ...
    }

    private void createTables(final SQLiteDatabase db, final String[] statements) {
        for (final String sql : statements) {
            try {
                db.execSQL(sql);
            } catch (final Exception e) {
                L.e("Unable to create table: " + sql, e);
            }
        }
    }
}

では、質問です。

  1. 私のロックは適切に実装されていますか? 私はこれに本当に慣れていないので、これReentrantLockが適切な選択であるかどうか、適切なタイミングでロック/ロック解除しているかどうかはわかりません
  2. 私のsynchronized方法は適切に実装されていますか? synchronizedつまり、並行スレッドによって中断されたくないメソッドの周りにキーワードを配置しました。これは正しいですか?私のsynchronized使い方についてアドバイスをいただけますか?
  3. 問題を再現するにはどうすればよいですか? DBへの同時読み取り/書き込みアクセスを行う3つのスレッドを使用するテストを作成し、いくつかを使用Thread.sleepして各スレッドからのdbのオープン/クローズが重​​複することを確認しましたが、クラッシュしません。これは本当に私を悩ませています。問題を抱えている人はあまりいないと思うので、再現方法がわかりません。
  4. DbAdapter+DbManager技術的な選択は良い考えですか? より良いパターンはありますか?
  5. DbManagerシングルトンを作るのは良い考えですか?
4

1 に答える 1

0

複数のスレッドがアクセスする場合は、シングルトン パターンを使用することをお勧めします。

このようにして、同じデータベースへの連続した呼び出しがシームレスにシリアル化されます。

NullPointerExceptionただし、挿入にいくつかの s を含めることは不可能ではありません。" " ロジックを拡張するには、次のThread.sleepコードを使用できます。

@Override
public SQLiteDatabase getWritableDatabase() {
    while (true) {
        try {
            return super.getWritableDatabase();
        } catch (SQLiteDatabaseLockedException e) {
            System.err.println(e);
        }

        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            System.err.println(e);
        }
    }
}
于 2013-09-15T18:34:14.893 に答える