android sqlite3并发,并发访问sqlite如何做到线程安全?

假设我们的Android项目工程中有一个SQLiteOpenHelper:public class DatabaseHelper extends SQLiteOpenHelper{

...

}复制代码

现在我们有Thread 1和Thread 2两个线程要往数据库中插入数据:// Thread 1

Context context = getApplicationContext();

DatabaseHelper helper = new DatabaseHelper(context);

SQLiteDatabase database = helper.getWritableDatabase();

database.insert(......);

database.close();

// Thread 2

Context context = getApplicationContext();

DatabaseHelper helper = new DatabaseHelper(context);

SQLiteDatabase database = helper.getWritableDatabase();

database.insert(......);

database.close();复制代码

那么我们可能会收到下如下错误logcat,并且其中一个线程的插入写数据库操作将会执行失败:android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)复制代码

为什么会发生SQLiteDatabaseLockedException异常呢?

因为每一次我们新创建new一个SQLiteOpenHelper对象,都会新建立一个数据库连接database connection。如果同时用两个不同的connection往数据库中写数据,其中一个将会失败,并收到database is locked异常。多线程操作数据库的情况下,我们应该要确保我们每一个线程中使用的是同一个数据库连接。

如何确保多线程先使用的是同一个数据库连接database connection?

使用单例类。下面我们就来实现一个持有并且能够返回一个 SQLiteOpenHelper对象的 DatabaseManager单例类:public class DatabaseManager{

private static DatabaseManager instance;

private static SQLiteOpenHelper mDatabaseHelper;

public static synchronized void initializeInstance(SQLiteOpenHelper helper){

if (instance == null) {

instance = new DatabaseManager();

mDatabaseHelper = helper;

}

}

public static synchronized DatabaseManager getInstance(){

if (instance == null) {

throw new IllegalStateException(DatabaseManager.class.getSimpleName() +

" is not initialized, call initialize(..) method first.");

}

return instance;

}

public synchronized SQLiteDatabase getDatabase(){

return mDatabaseHelper.getWritableDatabase();

}

}复制代码

现在我们来修改一下上面两个线程往数据库中插入数据的代码:// In your application class

DatabaseManager.initializeInstance(new DatabaseHelper());

// Thread 1

DatabaseManager manager = DatabaseManager.getInstance();

SQLiteDatabase database = manager.getDatabase()

database.insert(......);

database.close();

// Thread 2

DatabaseManager manager = DatabaseManager.getInstance();

SQLiteDatabase database = manager.getDatabase()

database.insert(......);

database.close();复制代码

现在代码就不会在出现 SQLiteDatabaseLockedException异常了,但是却还会有可能有另外一种crash发生:java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase复制代码

那为什么会出现attempt to re-open an already-closed object: SQLiteDatabase异常呢?

当使用同一个数据库连接database connection时,Thread 1以及Thread 2调用getDatabase()方法会返回同一个SQLiteOpenHelper对象。这样可能会导致这么一种情况出现:当Thread 1先调用database.close()关闭了数据库,而Thread 2接着调用了database.insert()想往数据库中插入数据,但是这时候数据库已经处于关闭状态了,于是系统报了attempt to re-open an already-closed object: SQLiteOpenHelper异常。

我们应该确保在没有人使用数据库时才关闭数据库。在 StackOverflow 上推荐的做法是永远不要关闭数据库,Android系统会尊重你的这种作法,但是会提示一下logcat信息,所以这属于一种不推荐的做法:Leak found

Caused by: java.lang.IllegalStateException: SQLiteDatabase created and never closed复制代码

那怎样确保多线程环境下不出现attempt to re-open an already-closed object: SQLiteDatabase异常呢?

一个可行的方法就是计数法,分别对打开以及关闭数据库的次数进行计数。

下面给出一个示例代码:public class DatabaseManager{

private AtomicInteger mOpenCounter = new AtomicInteger();

private static DatabaseManager instance;

private static SQLiteOpenHelper mDatabaseHelper;

private SQLiteDatabase mDatabase;

public static synchronized void initializeInstance(SQLiteOpenHelper helper){

if (instance == null) {

instance = new DatabaseManager();

mDatabaseHelper = helper;

}

}

public static synchronized DatabaseManager getInstance(){

if (instance == null) {

throw new IllegalStateException(DatabaseManager.class.getSimpleName() +

" is not initialized, call initializeInstance(..) method first.");

}

return instance;

}

public synchronized SQLiteDatabase openDatabase(){

if(mOpenCounter.incrementAndGet() == 1) {

// Opening new database

mDatabase = mDatabaseHelper.getWritableDatabase();

}

return mDatabase;

}

public synchronized void closeDatabase(){

if(mOpenCounter.decrementAndGet() == 0) {

// Closing database

mDatabase.close();

}

}

}复制代码

使用方法:SQLiteDatabase database = DatabaseManager.getInstance().openDatabase();

database.insert(...);

// database.close(); Don't close it directly!

DatabaseManager.getInstance().closeDatabase(); // correct way复制代码

我们每次使用数据库,都调用DatabaseManager的openDatabase()方法获取SQLiteDatabase,在这个方法里,有一个原子操作的计数器mOpenCounter,它记录了数据库被尝试打开的次数(即openDatabase()方法的调用次数),每调用一次openDatabase(),mOpenCounter就自增一。如果计数器数值为1,意味着要创建一个SQLiteDatabase对象返回,如果数值不为1,证明数据库已经被打开了,直接返回已经获取到的SQLiteDatabase对象就行了。

同理,DatabaseManager的closeDatabase()也是一样,每次调用这个方法,计数器mOpenCounter都自减一,当值为0的时候,就会真正调用mDatabase.close()方法关闭数据库。

现在我们就能在确保线程安全的情况下去放心地使用Sqlite数据库了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值