Android多线程下安全访问数据库
博客专区 > jdroid 的博客 > 博客详情
Android多线程下安全访问数据库
jdroid 发表于4年前
Android多线程下安全访问数据库
  • 发表于 4年前
  • 阅读 2968
  • 收藏 23
  • 点赞 0
  • 评论 1

腾讯云 技术升级10大核心产品年终让利>>>   

为了记录如何线程安全地访问你的Android数据库实例,我写下了这篇小小札记。文章中引用的项目代码请点击这里

      假设你已编写了自己的 SQLiteOpenHelper

public class DatabaseHelper extends SQLiteOpenHelper { ... }

        现在你想在不同的线程中对数据库进行写数据操作:

// 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)

       上面问题的出现,源于你每创建一个 SQLiteOpenHelper  对象时,实际上也是在新建一个数据库连接。如果你尝试通过多个连接同时对数据库进行写数据操作,其一定会失败。

       为确保我们能在多线程中安全地操作数据库,我们需要保证只有一个数据库连接被占用。

       我们先编写一个负责管理单个 SQLiteOpenHelper 对象的单例 DatabaseManager 。 

public class DatabaseManager {

    private static DatabaseManager instance;
    private static SQLiteOpenHelper mDatabaseHelper;

    public static synchronized void initialize(Context context, 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 new mDatabaseHelper.getWritableDatabase();
    }

}

        为了能在多线程中进行写数据操作,我们得修改一下代码,具体如下: 

// In your application class
 DatabaseManager.initializeInstance(getApplicationContext());

 // 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();

        然后又导致另个崩毁

java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase

        既然我们只有一个数据库连接,Thread1 和 Thread2 对方法 getDatabase() 的调用就会取得一样的 SQLiteDatabase 对象实例。之后的事情就是,当 Thread1 尝试管理数据库连接时,Thread2 却仍然在使用该数据库连接。这也就是导致 IllegalStateException 崩毁的原因。

        因此我们只能在确保数据库没有再被占用的情况下,才去关闭它。在 stackoveflow 上有一些讨论推荐“永不关闭”你的 SQLiteDatabase 。  如果你这样做,你的logcat将会出现以下的信息,因此我不认为这是一个好主意。

Leak foundCaused by: java.lang.IllegalStateException: SQLiteDatabase created and never closed

       示例:

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()。在方法里面,内置一个标志数据库被打开多少次的计数器。如果计数为1,代表我们需要打开一个新的数据库连接,否则,数据库连接已经存在。

在方法 closeDatabase() 中,情况也一样。每次我们调用 closeDatabase() 方法,计数器都会递减,直到计数为0,我们就需要关闭数据库连接了。

         提示: 你应该使用 AtomicInteger 来处理并发的情况

         现在你可以线程安全地使用你的数据库连接了。


         原文: https://github.com/dmytrodanylyk/dmytrodanylyk/blob/gh-pages/articles/Concurrent%20Database%20Access.md 


共有 人打赏支持
粉丝 17
博文 84
码字总数 23247
评论 (1)
kaient
db本身就有判断多线程的方法,如果当前没有线程在用,可以直接操作,如果有,用原子操作。greenDAO就是这样操作的。看他源码才知道。
×
jdroid
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: