并发访问sqlite有关异常问题处理

该文由 dmytrodanylyk.com/articles/co…翻译而来

假设我们的Android项目工程中有一个SQLiteOpenHelper

public class DatabaseHelper extends SQLiteOpenHelper { 
    ... 
}复制代码

现在我们有Thread 1Thread 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 {
<span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> DatabaseManager instance;
<span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> SQLiteOpenHelper mDatabaseHelper;

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">synchronized</span> <span class="hljs-keyword">void</span> <span class="hljs-title">initializeInstance</span><span class="hljs-params">(SQLiteOpenHelper helper)</span> </span>{
    <span class="hljs-keyword">if</span> (instance == <span class="hljs-keyword">null</span>) {
        instance = <span class="hljs-keyword">new</span> DatabaseManager();
        mDatabaseHelper = helper;
    }
}

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">synchronized</span> DatabaseManager <span class="hljs-title">getInstance</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-keyword">if</span> (instance == <span class="hljs-keyword">null</span>) {
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> IllegalStateException(DatabaseManager.class.getSimpleName() +
                <span class="hljs-string">" is not initialized, call initialize(..) method first."</span>);
    }

    <span class="hljs-keyword">return</span> instance;
}

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">synchronized</span> SQLiteDatabase <span class="hljs-title">getDatabase</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-keyword">return</span> 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 {
<span class="hljs-keyword">private</span> AtomicInteger mOpenCounter = <span class="hljs-keyword">new</span> AtomicInteger();

<span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> DatabaseManager instance;
<span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> SQLiteOpenHelper mDatabaseHelper;
<span class="hljs-keyword">private</span> SQLiteDatabase mDatabase;

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">synchronized</span> <span class="hljs-keyword">void</span> <span class="hljs-title">initializeInstance</span><span class="hljs-params">(SQLiteOpenHelper helper)</span> </span>{
    <span class="hljs-keyword">if</span> (instance == <span class="hljs-keyword">null</span>) {
        instance = <span class="hljs-keyword">new</span> DatabaseManager();
        mDatabaseHelper = helper;
    }
}

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">synchronized</span> DatabaseManager <span class="hljs-title">getInstance</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-keyword">if</span> (instance == <span class="hljs-keyword">null</span>) {
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> IllegalStateException(DatabaseManager.class.getSimpleName() +
                <span class="hljs-string">" is not initialized, call initializeInstance(..) method first."</span>);
    }

    <span class="hljs-keyword">return</span> instance;
}

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">synchronized</span> SQLiteDatabase <span class="hljs-title">openDatabase</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-keyword">if</span>(mOpenCounter.incrementAndGet() == <span class="hljs-number">1</span>) {
        <span class="hljs-comment">// Opening new database</span>
        mDatabase = mDatabaseHelper.getWritableDatabase();
    }
    <span class="hljs-keyword">return</span> mDatabase;
}

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">synchronized</span> <span class="hljs-keyword">void</span> <span class="hljs-title">closeDatabase</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-keyword">if</span>(mOpenCounter.decrementAndGet() == <span class="hljs-number">0</span>) {
        <span class="hljs-comment">// Closing database</span>
        mDatabase.close();

    }
}

}复制代码

使用方法:

SQLiteDatabase database = DatabaseManager.getInstance().openDatabase();
database.insert(…);
// database.close(); Don’t close it directly!
DatabaseManager.getInstance().closeDatabase(); // correct way复制代码

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

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

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


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值