我正在尝试获得一个对我的sqlite数据库的多线程访问没有失败的模式.另外,让我疯狂的是我无法重现这个问题.
我有一个使用数据库的应用程序,还有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,由想要管理单个表的子类扩展
>一个管理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等方法.
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关键字放在我不希望被并发线程中断的方法周围.这是正确的吗?你可以就我的同步使用提出建议吗?
>我该如何重现这个问题?我创建了一个测试,它使用3个线程对数据库进行并发读/写访问,并使用一些Thread.sleep来确保每个线程的db打开/关闭重叠,但它不会崩溃.这真是烦我,我不认为有很多人有这个问题,所以我不知道如何重现.
>我的DbAdapter DbManager技术选择是个好主意吗?有更好的模式吗?
>让DbManager成为单身人士是个好主意吗?
解决方法
对于多线程访问,建议使用单例模式.
但是,在插入上有一些NullPointerExceptions并非不可能.因此,要扩展“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); } } }