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