我正在尝试获取一个不会因多线程访问我的SQLite数据库而失败的模式。另外,让我感到疯狂的是我无法重现这个问题。
我的应用程序使用了数据库,同时还使用了Android账户和Android同步来同步我的应用程序数据。我猜想当两者同时发生时,它就会崩溃。我收到了很多类似以下的错误:
也许它们并不都与同一根本原因有关,但我有点迷失了。
我的应用程序使用了数据库,同时还使用了Android账户和Android同步来同步我的应用程序数据。我猜想当两者同时发生时,它就会崩溃。我收到了很多类似以下的错误:
* 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
,它由子类继承,这些子类想要管理单个表 - 一个名为
DbManager
的管理 SQLite 数据库的类,其中包含一个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
等方法。
这是数据库管理器:
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
使用提供建议? - 我该如何复现这个问题?我创建了一个测试,使用了 3 个线程对数据库进行并发读/写访问,并使用一些
Thread.sleep
来确保每个线程的数据库打开/关闭重叠,但它没有崩溃。这真的很困扰我,我认为没有很多人遇到这个问题,所以我不知道该如何复现。 - 我的
DbAdapter
+DbManager
技术选择是否是一个好主意?有更好的模式吗? - 将
DbManager
设为单例模式是一个好主意吗?