最近做项目时在多线程读写数据库时抛出了异常,这自然是我对SQlite3有理解不到位的地方,所以事后仔细探究了一番。
1.关于getWriteableDataBase()和getReadableDatabase()的真正作用
getWriteableDataBase()其实是相当于getReadableDatabase()的一个子方法,getWriteableDataBase()是只能返回一个以读写方式打开的SQLiteDatabase的引用,如果此时数据库不可写时就会抛出异常,比如数据库的磁盘空间满了的情况。而getReadableDatabase()一般默认是调用getWriteableDataBase()方法,如果数据库不可写时就会返回一个以只读方式打开的SQLiteDatabase的引用,这就是二者最明显的区别。
关键源码如下:
public synchronized SQLiteDatabase getWritableDatabase() { if (mDatabase != null) { if (!mDatabase.isOpen()) { // darn! the user closed the database by calling mDatabase.close() mDatabase = null; } else if (!mDatabase.isReadOnly()) { return mDatabase; // The database is already open for business } } ... ... public synchronized SQLiteDatabase getReadableDatabase() { if (mDatabase != null) { if (!mDatabase.isOpen()) { // darn! the user closed the database by calling mDatabase.close() mDatabase = null; } else { return mDatabase; // The database is already open for business } } ... ... try { return getWritableDatabase(); } ... ...
2.SQLiteDatabase的同步锁
其实在只使用一个SQLiteDatabase的引用时,SQLiteDatabase对CRUD操作都会加上一个锁(因为是db文件,所以精确至数据库级),这就保证了在同一时间你只能进行一项操作,无论是不是在同一个线程中,这就导致了如果你在程序中对SQLiteOpenHelper使用了单例模式,那么你对数据库读写进行任何的优化操作都是"徒劳"。
3.多线程读数据库
仔细看源码你会发现,在数据库操作中只有add,delete,update会调用lock(),而query()是不会调用的,但是在加载数据时,调用了SQLiteQuery的fillWindow方法,而该方法依然会调用SQLiteDatabase.lock(),所以要想真正的实现多线程读数据库,只能每个线程使用各自的SQLiteOpenHelper对象进行读操作,这样就可避开同步锁。关键源码如下:
/* package */ int fillWindow(CursorWindow window, int maxRead, int lastPos) { long timeStart = SystemClock.uptimeMillis(); mDatabase.lock(); mDatabase.logTimeStat(mSql, timeStart, SQLiteDatabase.GET_LOCK_LOG_PREFIX); try {... ...
4.多线程读写
实现多线程读写的关键是enableWriteAheadLogging属性,这个方法 API Level 11添加的,也就是所3.0以上的版本就基本不可能实现真正的多线程读写了。简单的说通过调用enableWriteAheadLogging()和disableWriteAheadLogging()可以控制该数据是否被运行多线程读写,如果允许,它将允许一个写线程与多个读线程同时在一个SQLiteDatabase上起作用。实现原理是写操作其实是在一个单独的log文件,读操作读的是原数据文件,是写操作开始之前的内容,从而互不影响。当写操作结束后读操作将察觉到新数据库的状态。当然这样做的弊端是将消耗更多的内存空间。
5.多线程写
这个就不用多想了,SQLite压根不支持,如果实在有需求可以使用多个数据库文件。
6.备注
(1)你有没有想SQLite最多支持多少个数据库连接,其实在官方API文档(enableWriteAheadLogging ()方法)中给出了最精确的答案:The maximum number of connections used to execute queries in parallel is dependent upon the device memory and possibly other properties.就是看你有多少内存,但是我感觉这话说的有点大,是不?哈哈。
(2)当你在多线程中只使用一个SQLiteDatabase的引用时,需要格外注意你SQLiteDataBase.close()调用的时机,因为你是使用的同一个引用,比如在一个线程中当一个Add操作结束后立刻关闭了数据库连接,而另一个现场中正准备执行查询操作,但此时db已经被关闭了,然后就会报异常错误。此时一般有三种解决方案,①简单粗暴给所有的CRUD添加一个 synchronized关键字;②永远不关闭数据库连接,只在最后退出是关闭连接。其实每次执行getWriteableDataBase()或getReadableDatabase()方法时,如果有已经建立的数据库连接则直接返回(例外:如果旧的连接是以只读方式打开的,则会在新建连接成功的前提下,关闭旧连接),所以程序中将始终保持有且只有一个数据库连接(前提是单例),资源消耗的很少。③可以自己进行引用计数,简单示例代码如下:
//打开数据库方法 public synchronized SQLiteDatabase openDatabase() { if (mOpenCounter.incrementAndGet() == 1) { // Opening new database try { mDatabase = sInstance.getWritableDatabase(); } catch (Exception e) { mDatabase = sInstance.getReadableDatabase(); } } return mDatabase; } //关闭数据库方法 public synchronized void closeDatabase() { if (mOpenCounter.decrementAndGet() == 0) { // Closing database mDatabase.close(); } }
(3)还有一些比较好的习惯和常识,例如关闭Cursor,使用Transaction,SQLite存储数据时其实不区分类型,以及SQLite支持大部分标准SQL语句,增删改查语句都是通用的等等。
前言 Java多线程分类中写了21篇多线程的文章,21篇文章的内容很多,个人认为,学习,内容越多、越杂的知识,越需要进行深刻的总结,这样才能记忆深刻,将知识变成自己的。这篇文章主要是对多线程的问题进行总结的,因此罗列了40个多线程的问题。 这些多线程的问题,有些来源于各大网站、有些来源于自己的思考。可能有些问题网上有;可能有些问题对应的答案也有;也可能有些各位网友也都看过。但是本文写作的重心就是所
本文向大家介绍HashMap 多线程操作导致死循环问题?相关面试题,主要包含被问及HashMap 多线程操作导致死循环问题?时的应答技巧和注意事项,需要的朋友参考一下 主要原因在于 并发下的Rehash 会造成元素之间会形成一个循环链表。不过,jdk 1.8 后解决了这个问题,但是还是不建议在多线程下使用 HashMap,因为多线程下使用 HashMap 还是会存在其他问题比如数据丢失。并发环境下
1):单线程应用程序只会在用户的CPU上使用1个线程吗?提供更多的线程会使用多个CPU内核吗?如果声明的线程比用户的CPU多,会发生什么?
本文向大家介绍Java程序员面试中的多线程问题总结,包括了Java程序员面试中的多线程问题总结的使用技巧和注意事项,需要的朋友参考一下 很多核心 Java 面试题来源于多线程(Multi-Threading)和集合框架(Collections Framework),理解核心线程概念时,娴熟的实际经验是必需的。这篇文章收集了 Java 线程方面一些典型的问题,这些问题经常被高级工程师所问到。 0、J
10月初投递简历,投了5个岗,10月21收到竞品分析岗面试邀请,至今无回音估计是凉了 1.自我介绍 2.与机械、车辆相关的课程 hr业务介绍:车辆结构原理、结构分析、设计前端,市场、用户、产品调研,自己公司的开发结构设计,竞品分析 3.实习中的竞品分析介绍,参与程度 4.未来职业规划是走技术岗还是管理岗 5.实习经历里最成功的的点,哪块发挥作用最大 6.校园经历介绍 7.校园经历里的项目如何管理团
问题内容: 最近,我正在阅读一个教程,其中遇到了一条声明: “Java语言规范保证了读取或写入的变量是一个原子操作(除非该变量的类型的或)类型的操作变量或者是只有当它们与申报原子的关键字。” 或提供类似的方法,并且其是原子的。 我对以上声明感到有些困惑。.请您澄清一下 何时使用 或 使用类。 问题答案: 否则(与作为一个)是一个原子操作。但是执行操作不是原子操作,因为它需要读取a的值,递增和写入a