本文微信公众号「AndroidTraveler」首发。

背景

本文是对一篇英文文档的翻译,原文请见文末链接。


并发数据库访问

假设你实现了自己的 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 对象,实际上你创建了新的数据库连接。如果你尝试从不同的连接同时对数据库写入数据,其中一个会失败。

为了在多线程使用数据库,我们要确保只使用一个数据库连接。

让我们构造单例类 DatabaseManager,它会持有并返回单个 SQLiteOpenHelper 对象。

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

这会带来另一个奔溃。

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

由于我们只使用了一个数据库连接,Thread1Thread2getDatabase() 方法都会返回同一个 SQLiteDatabase 对象实例。可能发生的场景是 Thread1 关闭了数据库,然而 Thread2 还在使用它。这也就是为什么我们会有 IllegalStateException 的奔溃的原因。

我们需要确保没有人正在使用数据库,这个时候我们才可以关闭它。stackoveflow 上有人推荐永远不要关闭你的 SQLiteDatabase。这会让你看到下面的 logcat 信息。所以我一点也不认为这是一个好的想法。

Leak found
Caused 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() 方法来说也是一样的。每次我们调用这个方法的时候,计数器在减少,当减为 0 的时候,我们关闭数据库连接。

现在你能够使用你的数据库并且确保是线程安全的。


原文:Concurrent database access

由于本人翻译水平有限,如果你有更好的翻译文案,欢迎在 GitHub 提 PR。

这边建了一个仓库,欢迎提 PR 投稿一些好的文章翻译。

并发数据库访问

小白到大神,你需要了解的 sqlite 最佳实践的更多相关文章

  1. 【同行说技术】iOS程序员从小白到大神必读资料汇总

    在文章<iOS程序员从小白到大神必读资料汇总(一)>里面介绍了很多iOS入门学习的资料,今天小编就发几篇技术进阶的文章,快来看看吧! 一.iOS后台模式开发指南 这个教程会教你在什么时候怎 ...

  2. 老杜告诉你java小白到大神是怎么炼成的(转载)

    老杜告诉你java小白到大神是怎么炼成的 1. 学习前的准备 一个好的学习方法(应该怎么学习更高效): 一个合格的程序员应该具备两个能力 有一个很好的指法速度(敲代码快) 有一个很好的编程思想(编程思 ...

  3. linux下鼠标穿透和取消穿透--linux小白,大神无视

    最近在用qt写一个跨平台的软件,因为设置了无边框,并且我自己给程序窗口加了阴影,阴影范围又比较大 所以必须给阴影区域加上鼠标穿透才能有更好的体验. 上网查了一下,在windows下使用SetWindo ...

  4. mui初级入门教程(四)— 再谈webview,从小白变“大神”!

    文章来源:小青年原创发布时间:2016-06-05关键词:mui,html5+,webview转载需标注本文原始地址: http://zhaomenghuan.github.io/#!/blog/20 ...

  5. 小白到大神,Python 密集知识点汇总

    Python 基础 1. 变量 你可以把变量想象成一个用来存储值的单词.我们看个例子. Python 中定义一个变量并为它赋值是很容易的.假如你想存储数字 1 到变量 "one" ...

  6. AtCoder从小白到大神的进阶攻略

    前言 现在全球最大的编程比赛记分网站非CodeForces和AtCoder莫属了,@ezoixx130大佬已经在去年介绍过CodeForces了(传送门),那么现在我们主要谈一下AtCoder. 简介 ...

  7. 自学python,从小白到大神,需要多久?

    2020年10月 TIOBE 排行榜超过了 Java, 历史上首次 Python 超越了 Java ,再次让许多朋友对 Python 产生了兴趣,今天我们来梳理下学习 Python 几个阶段或者级别, ...

  8. 【同行说技术】Python程序员小白变大神必读资料汇总( 三)

    在文章<Python开发.调试.爬虫类工具大全>里面向大家总结了各种实用工具和爬虫技术,今天小编收集了5篇带有实例干货的资料,赶紧来看看吧!另外,喜欢写博客的博主可以申请加工程师博主交流群 ...

  9. 通过开发工具发布web应用到tomcat服务器中--对于小白,大神可以忽略不看,勿喷,谢谢

    需要的工具 MyEclipse和TomCat 本人用的是MyEclipse2014和TomCat7 TomCat结构图 第一步:在MyEclipse中配置TomCat 如图所示: 第二步:创建Web项 ...

  10. React 生态系统:从小白到大神

    http://mp.weixin.qq.com/s/Epx46lznpnvgrIsbmAIZBA

随机推荐

  1. mac 开关机

    last | grep reboot (查看开机时间记录) last | grep shutdown (查看关机时间记录)

  2. http状态码_____ 204/206/200

    HTTP的状态码有很多种,主要有五个大类 1xx(临时响应) 2xx(成功) 3xx(已重定向) 4xx(请求错误) 5xx(服务器错误) 每个大类还对应一些具体的分类.平时我们接触比较多的是200. ...

  3. LightOj-1030 Discovering Gold (期望DP)

    You are in a cave, a long cave! The cave can be represented by a 1 x N grid. Each cell of the cave c ...

  4. A.Two Rival Students

    题目:两个竞争的学生 链接:(两个竞争的对手)[https://codeforces.com/contest/1257/problem/A] 题意:有n个学生排成一行.其中有两个竞争的学生.第一个学生 ...

  5. 强化学习环境OpenAi搭建,从虚拟机到Gym、Mujoco和mujoco-py的完整安装

    平时不怎么写博客,这次是因为环境的配置花费了我大概一个星期的时间.所以简单的记录一下搭建的整个过程,其中有些部分我直接推荐别人的博客的基本教程,都是我亲自尝试过成功的.同时,也希望这篇博客可以帮到您. ...

  6. LSI系列芯片Raid卡配置方法、管理手册

    说明 本手册适用于LSI芯片Raid卡 包括但不限于Inspur 2008/2108 Raid卡.LSI 9240/9260/9261/ 9271 等Raid卡. 不同型号的Raid卡在某些功能上的支 ...

  7. matlab画二叉树

    用matlab画如下图的二叉树. 先按树的先序遍历把结点编号,如下图所示. treeplot函数的序列用如下方法得到.从根结点开始先序遍历,依次写下父结点的编号,设根结点的父结点为0.按先序遍历,根结 ...

  8. 4种MySQL分页查询优化的方法,你知道几个?

    前言 当需要从数据库查询的表有上万条记录的时候,一次性查询所有结果会变得很慢,特别是随着数据量的增加特别明显,这时需要使用分页查询.对于数据库分页查询,也有很多种方法和优化的点.下面简单说一下我知道的 ...

  9. Js 与浮点数

    同步发表在我的博客:jmingzi 当你学习一个知识点没有方向时,可以尝试以解决问题的角度来理解它. 例如这个知识点我们可以从以下问题开始: 你看的到 1 真的是整数 1 吗? 为什么0.1 + 0. ...

  10. C语言每日一练——第7题

    一.题目要求 已知数据文件in.dat中存有200个四位数,把这些数存到数组a中,编写函数jsVal(),其功能是:把千位数字和十位数字重新组成一个新的含有两位数字的数ab(新数的十位数字是原四位数的 ...