12.android 多线程数据库读写分析与优化

11.多线程操作Sqlite?

====

最新需要给软件做数据库读写方面的优化,之前无论读写,都是用一个 SQLiteOpenHelper.getWriteableDataBase() 来操作数据库,现在需要多线程并发读写,项目用的是2.2的SDK。

android 的数据库系统用的是sqlite ,sqlite的每一个数据库其实都是一个.db文件,它的同步锁也就精确到数据库级了,不能跟别的数据库有表锁,行锁。

所以对写实在有要求的,可以使用多个数据库文件。

哎,这数据库在多线程并发读写方面本身就挺操蛋的。

下面分析一下不同情况下,在同一个数据库文件上操作,sqlite的表现。

测试程序在2.2虚拟手机,4.2.1虚拟手机,4.2.1真手机上跑。

1,多线程写,使用一个SQLiteOpenHelper。也就保证了多线程使用一个SQLiteDatabase。

先看看相关的源码

//SQLiteDatabase.java

public long insertWithOnConflict(String table, String nullColumnHack,

ContentValues initialValues, int conflictAlgorithm) {

if (!isOpen()) {

throw new IllegalStateException("database not open");

}

.... 省略

lock();

SQLiteStatement statement = null;

try {

statement = compileStatement(sql.toString());

// Bind the values

if (entrySet != null) {

int size = entrySet.size();

Iterator> entriesIter = entrySet.iterator();

for (int i = 0; i < size; i++) {

Map.Entry entry = entriesIter.next();

DatabaseUtils.bindObjectToProgram(statement, i + 1, entry.getValue());

}

}

// Run the program and then cleanup

statement.execute();

long insertedRowId = lastInsertRow();

if (insertedRowId == -1) {

Log.e(TAG, "Error inserting " + initialValues + " using " + sql);

} else {

if (Config.LOGD && Log.isLoggable(TAG, Log.VERBOSE)) {

Log.v(TAG, "Inserting row " + insertedRowId + " from "

+ initialValues + " using " + sql);

}

}

return insertedRowId;

} catch (SQLiteDatabaseCorruptException e) {

onCorruption();

throw e;

} finally {

if (statement != null) {

statement.close();

}

unlock();

}

}

//SQLiteDatabase.java

private final ReentrantLock mLock = new ReentrantLock(true);

/* package */ void lock() {

if (!mLockingEnabled) return;

mLock.lock();

if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING) {

if (mLock.getHoldCount() == 1) {

// Use elapsed real-time since the CPU may sleep when waiting for IO

mLockAcquiredWallTime = SystemClock.elapsedRealtime();

mLockAcquiredThreadTime = Debug.threadCpuTimeNanos();

}

}

}

通过源码可以知道,在执行插入时,会请求SQLiteDatabase对象的成员对象 mlock 的锁,来保证插入不会并发执行。

经测试不会引发异常。

但是我们可以通过使用多个SQLiteDatabase对象同时插入,来绕过这个锁。

2,多线程写,使用多个SQLiteOpenHelper,插入时可能引发异常,导致插入错误。

E/Database(1471): android.database.sqlite.SQLiteException: error code 5: database is locked08-01

E/Database(1471):     at android.database.sqlite.SQLiteStatement.native_execute(Native Method)

E/Database(1471):     at android.database.sqlite.SQLiteStatement.execute(SQLiteStatement.java:55)

E/Database(1471):     at android.database.sqlite.SQLiteDatabase.insertWithOnConflict(SQLiteDatabase.java:1549)

多线程写,每个线程使用一个SQLiteOpenHelper,也就使得每个线程使用一个SQLiteDatabase对象。多个线程同时执行insert, 最后调用到本地方法  SQLiteStatement.native_execute

抛出异常,可见android 框架,多线程写数据库的本地方法里没有同步锁保护,并发写会抛出异常。

所以,多线程写必须使用同一个SQLiteOpenHelper对象。

3,多线程读

看SQLiteDatabase的源码可以知道,insert  , update ,  execSQL   都会 调用lock(), 乍一看唯有query 没有调用lock()。可是。。。

仔细看,发现

最后,查询结果是一个SQLiteCursor对象。

SQLiteCursor保存了查询条件,但是并没有立即执行查询,而是使用了lazy的策略,在需要时加载部分数据。

在加载数据时,调用了SQLiteQuery的fillWindow方法,而该方法依然会调用SQLiteDatabase.lock()

/**

* Reads rows into a buffer. This method acquires the database lock.

*

* @param window The window to fill into

* @return number of total rows in the query

*/

/* 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 {

acquireReference();

try {

window.acquireReference();

// if the start pos is not equal to 0, then most likely window is

// too small for the data set, loading by another thread

// is not safe in this situation. the native code will ignore maxRead

int numRows = native_fill_window(window, window.getStartPosition(), mOffsetIndex,

maxRead, lastPos);

// Logging

if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {

Log.d(TAG, "fillWindow(): " + mSql);

}

mDatabase.logTimeStat(mSql, timeStart);

return numRows;

} catch (IllegalStateException e){

// simply ignore it

return 0;

} catch (SQLiteDatabaseCorruptException e) {

mDatabase.onCorruption();

throw e;

} finally {

window.releaseReference();

}

} finally {

releaseReference();

mDatabase.unlock();

}

}

所以想要多线程读,读之间没有同步锁,也得每个线程使用各自的SQLiteOpenHelper对象,经测试,没有问题。

4,多线程读写

我们最终想要达到的目的,是多线程并发读写

多线程写之前已经知道结果了,同一时间只能有一个写。

多线程读可以并发

所以,使用下面的策略:

一个线程写,多个线程同时读,每个线程都用各自SQLiteOpenHelper。

这样,在java层,所有线程之间都不会锁住,也就是说,写与读之间不会锁,读与读之间也不会锁。

发现有插入异常。

E/SQLiteDatabase(18263): Error inserting descreption=InsertThread#01375493606407

E/SQLiteDatabase(18263): android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)

E/SQLiteDatabase(18263):     at android.database.sqlite.SQLiteConnection.nativeExecuteForLastInsertedRowId(Native Method)

插入异常,说明在有线程读的时候写数据库,会抛出异常。

分析源码可以知道, SQLiteOpenHelper.getReadableDatabase() 不见得获得的就是只读SQLiteDatabase 。

// SQLiteOpenHelper.java

public synchronized SQLiteDatabase getReadableDatabase() {

if (mDatabase != null && mDatabase.isOpen()) {

return mDatabase; // The database is already open for business

}

if (mIsInitializing) {

throw new IllegalStateException("getReadableDatabase called recursively");

}

try {

return getWritableDatabase();

} catch (SQLiteException e) {

if (mName == null) throw e; // Can't open a temp database read-only!

Log.e(TAG, "Couldn't open " + mName + " for writing (will try read-only):", e);

}

SQLiteDatabase db = null;

try {

mIsInitializing = true;

String path = mContext.getDatabasePath(mName).getPath();

db = SQLiteDatabase.openDatabase(path, mFactory, SQLiteDatabase.OPEN_READONLY);

if (db.getVersion() != mNewVersion) {

throw new SQLiteException("Can't upgrade read-only database from version " +

db.getVersion() + " to " + mNewVersion + ": " + path);

}

onOpen(db);

Log.w(TAG, "Opened " + mName + " in read-only mode");

mDatabase = db;

return mDatabase;

} finally {

mIsInitializing = false;

if (db != null && db != mDatabase) db.close();

}

}

因为它先看有没有已经创建的SQLiteDatabase,没有的话先尝试创建读写 SQLiteDatabase ,失败后才尝试创建只读SQLiteDatabase 。

所以写了个新方法,来获得只读SQLiteDatabase

//DbHelper.java

//DbHelper extends SQLiteOpenHelper

public SQLiteDatabase getOnlyReadDatabase() {

try{

getWritableDatabase(); //保证数据库版本最新

}catch(SQLiteException e){

Log.e(TAG, "Couldn't open " + mName + " for writing (will try read-only):",e);

}

SQLiteDatabase db = null;

try {

String path = mContext.getDatabasePath(mName).getPath();

db = SQLiteDatabase.openDatabase(path, mFactory, SQLiteDatabase.OPEN_READONLY);

if (db.getVersion() != mNewVersion) {

throw new SQLiteException("Can't upgrade read-only database from version " +

db.getVersion() + " to " + mNewVersion + ": " + path);

}

onOpen(db);

readOnlyDbs.add(db);

return db;

} finally {

}

}

使用策略:一个线程写,多个线程同时读,只用一个SQLiteOpenHelper,读线程使用自己写的getOnlyReadDatabase()方法获得只读。

但是经过测试,还是会抛出异常,2.2上只有插入异常,4.1.2上甚至还有读异常。

4.1.2上测试,读异常。

E/SQLiteLog(18263): (5) database is locked

W/dalvikvm(18263): threadid=21: thread exiting with uncaught exception (group=0x41e2c300)

E/AndroidRuntime(18263): FATAL EXCEPTION: onlyReadThread#8

E/AndroidRuntime(18263): android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5): , while compiling: SELECT * FROM test_t

看来此路不同啊。

其实SQLiteDataBase 在API 11 多了一个 属性 ENABLE_WRITE_AHEAD_LOGGING。

可以打,enableWriteAheadLogging(),可以关闭disableWriteAheadLogging(),默认是关闭的。

这个属性是什么意思呢?

参考api文档,这个属性关闭时,不允许读,写同时进行,通过 锁 来保证。

当打开时,它允许一个写线程与多个读线程同时在一个SQLiteDatabase上起作用。实现原理是写操作其实是在一个单独的文件,不是原数据库文件。所以写在执行时,不会影响读操作,读操作读的是原数据文件,是写操作开始之前的内容。

在写操作执行成功后,会把修改合并会原数据库文件。此时读操作才能读到修改后的内容。但是这样将花费更多的内存。

有了它,多线程读写问题就解决了,可惜只能在API 11 以上使用。

所以只能判断sdk版本,如果3.0以上,就打开这个属性

public DbHelper(Context context , boolean enableWAL) {

this(context, DEFAULT_DB_NAME, null, DEFAULT_VERSION);

if( enableWAL && Build.VERSION.SDK_INT >= 11){

getWritableDatabase().enableWriteAheadLogging();

}

}

关于SQLiteDatabase的这个属性,参考api文档,也可以看看SQLiteSession.java里对多线程数据库读写的描述。

结论

想要多线程并发读写,3.0以下就不要想了,3.0以上,直接设置enableWriteAheadLogging()就ok。

如果还是达不到要求,就使用多个db文件吧。

另:单位有一个三星 note2手机,上面所有的例子跑起来都啥问题也没有。。。。很好很强大。

最后,附上我的测试程序。https://github.com/zebulon988/SqliteTest.git

java sqlite 多线程并发_sqlite之多线程总结相关推荐

  1. java 多线程并发 问题_JAVA多线程和并发基础面试问答

    原文链接 译文连接作者:Pankaj  译者:郑旭东  校对:方腾飞 多线程和并发问题是Java技术面试中面试官比较喜欢问的问题之一.在这里,从面试的角度列出了大部分重要的问题,但是你仍然应该牢固的掌 ...

  2. java 多线程 并发实例_Java 多线程(并发)

    线程释义 使用java.lang.Thread类或者java.lang.Runnable接口编写代码来定义.实例化和启动新线程. 一个Thread类实例只是一个对象,像Java中的任何其他对象一样,具 ...

  3. java多线程并发实例_JAVA多线程的并发控制|java多线程并发实例

    java的多线程实现主要有两种,一种是继承Thread,一种是实现Runnable接口,这个是java最基本的多线程知识.这里要补充一下,runnable接口中的run方法是不返回任何内容的,如果想返 ...

  4. python多线程并发写入_Python多线程并发的简单测试

    之前也写了一些简单的Python程序,对于多线程的并发一直没有涉及,今天决定先突破一下,把这个部分的内容先快速的掌握,然后在这个基础上细化改进. 我的好友冰川擅长Python技术,所以就拿来主义,参考 ...

  5. idea 断点线程_在IntelliJ IDEA中多线程并发代码的调试方法

    通常来说,多线程的并发及条件断点的debug是很难完成的,或许本篇文章会给你提供一个友好的调试方法.让你在多线程开发过程中的调试更加的有的放矢. 我们将通过一个例子来学习.在这里,我编写了一个多线程程 ...

  6. 对线程与进程的区别以及对多线程并发的理解

    一.线程与进程的区别 先简单说说线程与进程的概念: (1)进程是指一个内存中运行的应用程序,比如在Windows系统中,一个运行的exe就是一个进程. (2)线程是指进程中的一个执行流程. 区别: 一 ...

  7. Java增强之并发编程

    Java增强之并发编程 1 多线程 1.1 进程及线程 程序启动的时候,电脑会把这个程序加载到内存,在内存中需要给当前的程序分配一段的独立运行的空间,这个空间就专门负责这个程序的运行.每个应用程序运行 ...

  8. java sqlite 多线程并发_C#_C#解决SQlite并发异常问题的方法(使用读写锁),本文实例讲述了C#解决SQlite并 - phpStudy...

    C#解决SQlite并发异常问题的方法(使用读写锁) 本文实例讲述了C#解决SQlite并发异常问题的方法.分享给大家供大家参考,具体如下: 使用C#访问sqlite时,常会遇到多线程并发导致SQLI ...

  9. java线程钥匙_Java多线程并发编程/锁的理解

    一.前言 最近项目遇到多线程并发的情景(并发抢单&恢复库存并行),代码在正常情况下运行没有什么问题,在高并发压测下会出现:库存超发/总库存与sku库存对不上等各种问题. 在运用了 限流/加锁等 ...

最新文章

  1. [LeetCode 120] - 三角形(Triangle)
  2. php扩展的框架,新一代轻量级PHP扩展框架 Asf
  3. linux宝塔面板配置可道云,使用宝塔面板配合可道云打造私有云
  4. 一文读懂 volatile 关键字
  5. Ioc的推荐实现方式
  6. CVPR 2021 involution:超越convolution和self-attention的神经网络新算子
  7. C++STL常用集合算法
  8. 【好消息】Python和Excel终于可以互通了!!
  9. 【基础】嵌入式浏览器移植基本要素
  10. redis lru和lfu的实现
  11. 更改计算机复制快捷键,电脑上怎么把一句话设置快捷键,电脑快捷键复制一句话-...
  12. 老闪创业那些事儿(外传)——历经世事的魏爷
  13. 威feng网站的aes算法破解
  14. Matlab:拉盖尔-高斯光束单缝衍射
  15. 台式计算机操作系统的安装,台式电脑重装系统步骤图解
  16. 知我者谓我心忧 不知我者谓我何求
  17. qt屏幕漫天雪花飘落
  18. 中心性算法的简单总结
  19. 农夫过河算法最简便的c语言,C语言农夫过河
  20. 消防工程师 8.3 防排烟系统-排烟

热门文章

  1. unity头顶状态制作_Unity里名字牌、血条等头顶挂件的实现
  2. 区块链100讲:从宠物商店案例看DAPP架构和WEB3.JS交互接口
  3. 趋势交易策略的买卖点选择,几种趋势介绍
  4. 三轴陀螺仪MPU3050驱动解析
  5. 源码编译安装LNMP
  6. MotionEvent(二) 多点触摸
  7. final与static
  8. IPTV终端与机顶盒技术
  9. chrome断网小游戏实现
  10. 竞赛:汽车领域多语种迁移学习挑战赛(科大讯飞)