本文微信公众号「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. 创建raid10(五块磁盘)

    创建raid10(五块磁盘) Raid10(5块磁盘) 上一个bolg已经做了raid5(3 个raid. 2个备份),在此条件下继续以下步骤: 1.先将磁盘系统卸载  2.看系统挂载里是否还有md0 ...

  2. jeecg培训第一课(代码生成与权限分配)

    问题描述:进口部要完成一票进口报关单的增删改查,操作员张三登录只能增删改张三的报关单,操作员李四登录只能增删改李四的报关单, 部门主管王五登录能查看张三和李四的报关单,但不能修改删除.操作员能提交报关 ...

  3. SpringBoot整合邮件发送

    本节介绍SpringBoot项目如何快速配置和发送邮件,包括简单的邮件配置.发送简单邮件.发送HTML邮件.发送携带附件的邮件等. 示例源码在:https://github.com/laolunsi/ ...

  4. Spring之跨重定向请求传递数据

    摘要 在开发场景中,大部分数据都是使用请求转发(forward)进行传递,而使用重定向(redirect)传递数据可能比较少. 那么问题来了:请求中的数据生命周期存活时间只在一个请求转发(reques ...

  5. UVA-10391 Compoud Words

    You are to find all the two-word compound words in a dictionary. A two-word compound word is a word ...

  6. unix环境高级编程中的err_quit,err_sys用到的知识点

    unix环境高级编程中的err_quit,err_sys 环境 os CentOS release 6.7 (Final) gcc 4.4.7 c语言预备知识 标准输入输出文件 在linux系统中一切 ...

  7. asp.net core 拦击器制作的权限管理系统DEMO

    效果图 没有登陆不会执行请求日期的方法,不管是否登陆都不允许访问请求时间方法 验证不通过是会进行转发到Home/error方法中, 代码附上: [Route("[controller]/[a ...

  8. 新版FPC摄像头测评 OV7725 OV7670 OV9650 OV9655 OV5640 OV5642 OV2640 OV3640 MT9D112

    新版FPC摄像头测评 OV7725 OV7670 OV9650 OV9655 OV5640 OV5642 OV2640 OV3640 MT9D112 最新制样新版FPC摄像头板卡,先看看结构尺寸 再瞧 ...

  9. 【JS】332- 为什么我更喜欢对象而不是 switch 语句

    昨天偷懒了,欢迎点击关注???这样我就多更大的动力日更了- 正文从这里开始~~~ 最近(或者不是最近,这完全取决于您什么时候阅读这边文章),我正在跟我的团队伙伴讨论如何去处理这种需要根据不同的值去处理 ...

  10. win10 修改用户目录为英文

    win10家庭版怎样进入 administratornet user administrator /active:yes net user administrator /active:no