- @H_404_8@多线程读写
我们可以得知 sqlite 是文件级别的锁:多个线程可以同时读,但是同时只能有一个线程写。 Android 提供了 sqliteOpenHelper 类,加入 Java 的锁机制以便调用。
如果多线程同时读写(这里的指不同的线程用使用的是不同的 Helper 实例),后面的就会遇到 android.database.sqlite.sqliteException: database is locked 这样的异常。
对于这样的问题,解决的办法就是 keep single sqlite connection , 保持单个 sqliteOpenHelper 实例,同时对所有数据库操作的方法添加 synchronized 关键字。
如下所示:
Android为我们提供了sqliteOpenHelper类,我们可以通过getWritableDatabase 或者 getReadableDatabase 拿到 sqliteDatabase 对象,然后执行相关方法。这 2 个方法名称容易给人误解,我也在很长的一段时间内想当然的认为 getReadabeDatabase 就是获取一个只读的数据库,可以获取很多次,多个线程同时读,用完就关闭,实际上 getReadableDatabase 先以读写方式打开数据库,如果数据库的磁盘空 间满了,就会打开失败,当打开失败后会继续尝试以只读方式打开数据库。
代码:
public synchronized sqliteDatabase getReadableDatabase() {
if (mDatabase != null && mDatabase.isOpen()) {
return mDatabase;// The database is already open for business
}
if (mIsInitializing) {
throw new IllegalStateException("getReadableDatabase called recursively");
}
try {
return getWritableDatabase();
} catch (sqliteException e) {
if (mName == null) throw e;// Can't open a temp database read-only!
Log.e(TAG,"Couldn't open " + mName + " for writing (will try read-only):",e);
}
sqliteDatabase db = null;
try {
mIsInitializing = true;
String path = mContext.getDatabasePath(mName).getPath();
db = sqliteDatabase.openDatabase(path,mFactory,sqliteDatabase.OPEN_READONLY);
if (db.getVersion() != mNewVersion) {
throw new sqliteException("Can't upgrade read-only database from version " +
db.getVersion() + " to " + mNewVersion + ": " + path);
}
onOpen(db);
Log.w(TAG,"Opened " + mName + " in read-only mode");
mDatabase = db;
return mDatabase;
} finally {
mIsInitializing = false;
if (db != null && db != mDatabase) db.close();
}
}
在多线程中,如果第一个线程先调用getWritableDatabase,后面线程再次调用,或者第一个线程先调用getReadableDatabase,后面的线程调用getWritableDatabase,那么后面的这个方法是会失败的,因为数据库文件打开后会加锁,必须等前面的关闭后后面的调用才能正常执行,正是因为这个原因,可以1 Write+Many Read(有可能产生冲突,因为第一个getReadableDatabase有可能先于getWritableDatabase执行,导致后面的失败),也可以Many Read,但是不可能Many Write。所以使用单例加上同步的数据库操作方法,就不会出现死锁的问题,这部分例子请参照附件,多线程可以运行的很好,另外关于sqlite database locking collisions example,网上有很不错的一个例子,可以 这里 去下载。
其实我觉得理论上可以修改getReadableDatabase方法,打开的数据库都是Read Only的,这样就能同时1 Write+Many Read,只不过要保证打开之前,数据库要创建或者升级好,这样读操作就不会互斥写操作,效率相对更高。
关于数据库关闭的问题,在下面好的习惯中会专 门说明。
代码:
db.beginTransaction();
try {
...
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}