前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >高效解决「SQLite」数据库并发访问安全问题,只这一篇就够了

高效解决「SQLite」数据库并发访问安全问题,只这一篇就够了

作者头像
圆号本昊
发布2021-09-24 11:50:32
2.3K0
发布2021-09-24 11:50:32
举报
文章被收录于专栏:github@hornhuanggithub@hornhuang

Concurrent database access


本文译自:https://dmytrodanylyk.com/articles/concurrent-database/

对于 Android Dev 而言,有关 SQLite 的操作再经常不过了,相比你一定经历过控制台一片爆红的情况,这不禁让我们疑问:SQLite 到底是线程安全的吗?

OK 废话不多说,我们 ⬇️

直接开始


首先,假设你已经实现了一个 SQLiteHelper 类,如下所示:
代码语言:javascript
复制
public class DatabaseHelper extends SQLiteOpenHelper { ... }

现在你想要在两个子线程中,分别地向 SQLite 里写入一些数据:

代码语言:javascript
复制
 // 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();

对吧?看上去很 OK 没啥毛病。

那么这时,我们点一下 run ,gio~ 你将会在你的 logcat 里收到如下礼物「报错」:

代码语言:javascript
复制
android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)
到底是怎么回事呢?

我们分析一下报错终于发现:这是由于你每次创建 SQLiteHelper 时,都对数据库进行了一个链接操作。这时,如果你尝试着,同时从实际不同的链接中,对数据库进行写入操作,失败就是必然的了。

总结一下 如果我们想再不同的线程中,对数据库进行包括读写操作在内的任何使用,我们就必须得确保,我们使用的是同一个的连接

好,那现在问题就明了了。现在让我们创建一个单例模式类:DatabaseManager 用来创建和返回唯一的,单例 DatabaseManager 对象。

ps 有些同学问我什么是单例模式,我专门跑去写了这篇博客来解释下,单例模式-全局可用的 context 对象,这一篇就够了码字不易帮我点个赞谢谢 ?

代码语言:javascript
复制
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();
    }

}

现在,我们在回来修改下之前的代码,结果如下所示:

代码语言:javascript
复制
// 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();

逻辑比之前更清晰,代码冗余也少了。现在我们在跑下代码,这时我们会收到,另一个 cache

代码语言:javascript
复制
java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase

不要慌,我们仔细分析下报错,我们发现:单例模式的使用保证了我们,在线程一、二「Thread 1、Thread 2 中」只会获得到唯一的 SQLiteHelper 对象,但这时问题就来了,当我们运行完线程一「Thread 1」时,我们的 database.close(); 已经替我们关闭了对数据库的连接,但与此同时我们的线程二「Thread 2」依然保持这对 SQLiteHelper 的引用。正是这个原因,我们收到了IllegalStateException的报错。

所以,这时我们就需要保证,当没有人使用 SQLiteHelper 时,再将其断开连接。

保证 SQLIiteHelper 在无人使用时才断开连接

关于这个问题的解决 stackoveflow 上很多人建议我们:永远不要断开 SQLiteHelper 的连接,但是这样以来你会在 logcat 上得到如下输出:

代码语言:javascript
复制
Leak found
Caused by: java.lang.IllegalStateException: SQLiteDatabase created and never closed

所以,我非常不建议你用这个方法。为了解决这个问题,我们引入计数器的概念

标准样例

通过如下方法,你将通过一个计数器来完美解决 打开/关闭 数据库连接的问题:

代码语言:javascript
复制
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();

        }
    }
}

我们在线程中可以这样使用它:

代码语言:javascript
复制
SQLiteDatabase database = DatabaseManager.getInstance().openDatabase();
database.insert(...);
// database.close(); Don't close it directly!
DatabaseManager.getInstance().closeDatabase(); // correct way

每当你需要使用数据库时,你只要调用 DatabaseManager 中的 openDatabase() 方法。在这个方法中,我们有一个,用来记录数据库被“打开”了几次的 mOpenCounter 对象。当它等于 1 时,这意味着你需要去创建新的数据库连接来使用数据库,否则的话,就说明数据库已经在使用中了。

同样的情况也发生在 closeDatabase() 方法中,当你每次调用该方法时,我们的 mOpenCounter 对象就会减一。当它减到 0 时,我们就去关闭这个数据库的连接。

完美,最后:

  1. 现在你就能随心所欲的使用你的数据库,而且你可以相信 – 它是线程安全的了!
  2. 当然很多同学对数据库的使用,还有着很多的疑惑,我后期将会针对数据库的使用,作出一系列总结,有兴趣可以继续关注 _yuanhao 的编程世界

相关文章


每个人都要学的图片压缩终极奥义,有效解决 Android 程序 OOM

Android 让你的 Room 搭上 RxJava 的顺风车 从重复的代码中解脱出来

ViewModel 和 ViewModelProvider.Factory:ViewModel 的创建者

单例模式-全局可用的 context 对象,这一篇就够了

缩放手势 ScaleGestureDetector 源码解析,这一篇就够了

看完这篇还不会 GestureDetector 手势检测,我跪搓衣板!

Android 属性动画框架 ObjectAnimator、ValueAnimator ,这一篇就够了

Android 逐帧动画( Drawable 动画),这一篇就够了

看完这篇再不会 View 的动画框架,我跪搓衣板

Android 自定义时钟控件 时针、分针、秒针的绘制这一篇就够了

请点赞!因为你的鼓励是我写作的最大动力!


关于源码 Demo 链接:Demo 码了好几天才整完,希望大家点个 star~ 谢谢!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019/10/07 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Concurrent database access
  • 直接开始
  • 相关文章
    • 请点赞!因为你的鼓励是我写作的最大动力!
    相关产品与服务
    数据库
    云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档