SQLiteDatabase 多线程访问需要注意的问题
来源:互联网 发布:自学跆拳道的软件 编辑:程序博客网 时间:2024/04/27 23:13
SQLiteDatabase 多线程访问需要注意的问题
1. 多线程打开SQLiteDatabase
// Thread 1Context context = getApplicationContext();DatabaseHelper helper = new DatabaseHelper(context);SQLiteDatabase database = helper.getWritableDatabase();database.insert(…);database.close();// Thread 2Context context = getApplicationContext();DatabaseHelper helper = new DatabaseHelper(context);SQLiteDatabase database = helper.getWritableDatabase();database.insert(…);database.close();执行上面代码可能会遇到Exception:
android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)
上面问题的出现源于你创建了多个SQLiteOpenHelper, 每个SQLiteOpenHelper都会打开一个数据库对象,实际上也就是对同一个数据库文件建立了多个连接connection。如果你通过多个连接 connection 同时对数据库进行读/写数据操作,则会遇到exception。
SQLiteDatabase数据库本身是“线程安全的”:看SQLiteDatabase的源码可以知道,insert , update , execSQL都会调用lock(), 乍一看唯有query没有调用lock()。可是仔细看,发现query查询结果是一个SQLiteCursor对象,SQLiteCursor保存了查询条件,但是并没有立即执行查询,而是使用了lazy的策略,在需要时加载部分数据,在加载数据时调用了SQLiteQuery的fillWindow方法,而该方法依然会调用SQLiteDatabase.lock()。所以SQLiteDatabase任何读写都是有同步锁的。这个锁是文件级别的,对db文件操作需拿到这把锁。
对同一个SQLiteDatabase对象,在不同线程同时调用读/写函数,是不会有问题的,SQLiteDatabase会自己同步读/写操作。但是当对多个SQLiteDatabase对象(指向同一个db),在多线程同时调用读/写函数,SQLite数据库自身的安全约束遭到破坏,导致多线程同时读写数据库冲突。
解决的办法就是使用单例模式来严格控制SQLiteDatabase的实例,也就是只能实例化一个SQLiteDatabase对象,无论多少个线程都只用这一个SQLiteDatabase对象来读写线程。Android 提供了SQLiteOpenHelper类,它对SQLiteDatabase做了封装,在一个SQLiteOpenHelper类中,保证读写操作都是对同一个SQLiteDatabase对象进行;也就是共用一个数据库链接connection,从而避免多个链接同时读写产生Exception。
然而,我们在调用过程中往往不经意间会 new SQLiteOpenHelper 多个对象,这样同样会产生多个数据库链接connection,一定要保证多线程的读/写操作都是发生在一个库链接connection上,才不会有问题。
我们先编写一个负责管理单个 SQLiteOpenHelper 对象的单例 DatabaseManager:
public class DatabaseManager { private static DatabaseManager instance; private static SQLiteOpenHelper mDatabaseHelper; public static synchronized void initialize(Context context, 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 new mDatabaseHelper.getWritableDatabase(); }}为了能在多线程中进行写数据操作,我们得修改一下代码,具体如下:
// In your application class DatabaseManager.initializeInstance(getApplicationContext()); // 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既然我们只有一个数据库连接,Thread1 和 Thread2 对方法 getDatabase() 的调用就会取得一样的 SQLiteDatabase 对象实例。当 Thread1 尝试关闭数据库连接时,Thread2 却仍然在使用该数据库连接,这也就是导致 IllegalStateException 崩毁的原因。因此我们只能在确保数据库没有再被占用的情况下,才去关闭它。有一些讨论推荐“永不关闭”你的 SQLiteDatabase。如果你这样做,你的logcat将会出现以下的信息,因此我不认为这是一个好主意。
Leak foundCaused by: java.lang.IllegalStateException: SQLiteDatabase created and never closed我们可以做一个open 计数,以后每当你需要使用数据库连接,你可以通过调用类 DatabaseManager 的方法openDatabase()。在方法里面,内置一个标志数据库被打开多少次的计数器。如果计数为1,代表我们需要打开一个新的数据库连接,否则,数据库连接已经存在。在方法 closeDatabase() 中,情况也一样。每次我们调用 closeDatabase() 方法,计数器都会递减,直到计数为0,我们就需要关闭数据库连接了。
private AtomicInteger mOpenCounter = new AtomicInteger();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(); }}
2. 多线程访问SQLiteDatabase单例
SQLiteDatabse实质上是将数据写入一个文件,我们可以得知SQLiteDatabse是文件级别的锁。多个线程可以同时读;但是多个线程同时读/写时:如读先发生,写会被阻塞直至读完毕,写才会继续执行;如写先发生,读会被阻塞直至写完毕,读才会继续执行。
Android为我们提供了SqliteOpenHelper类,我们可以通过getWritableDatabase或者getReadableDatabase拿到SQLiteDatabase对象,然后执行相关方法。getReadabeDatabase就是获取一个只读的数据库,可以获取很多次,多个线程同时读。在多线程中如果第一个线程先调用getReadableDatabase,后面的线程调用getWritableDatabase,调用都会返回SQLiteDatabse对象,但是对SQLiteDatabse同时进行读/写操作会被阻塞,SQLiteDatabse会自动同步读/写(即 write 和write 互斥,read和write互斥,但是read 和 read 不互斥)。
关闭SqliteOpenHelper:
在某些单例 SqliteOpenHelper 例子中,其实一直没有关闭数据库,但是我们再次调用 getReadabeDatabase 和 getWritableDatabas 的方法,它们会关闭old SQLiteDatabase的,我们只需要在Application的onTerminal方法中关闭即可,这样也能避免多线程中,一个线程关闭了数据库,导致其他线程使用的时候失败的问题。实质上,数据库是一个文件引用,单例模式下不关闭也不会出现问题,让它保持随单例的生命周期就好了。
- SQLiteDatabase 多线程访问需要注意的问题
- 多线程(多线程需要注意的问题)
- 多线程开发中需要注意的问题
- 多线程编程需要注意的问题
- Win32多线程之设计多线程程序需要注意的问题
- .net下访问Access数据库需要注意的问题
- Queue<T> 需要注意的多线程冲突问题
- 多线程下使用使用 UniDAC+MSSQL 需要注意的问题
- 多线程+Webservice分布式编程时需要注意的COM问题
- Queue<T> 需要注意的多线程冲突问题
- java Vector 在多线程使用中需要注意的问题
- STL list在多线程下使用需要注意的问题
- 需要注意的问题
- 需要注意的问题
- CURL多线程处理需要注意问题
- 对于多线程访问同一变量是否需要加锁的问题
- 多线程原理、线程安全函数和多线程程序需要注意的问题
- 多线程原理、线程安全函数和多线程程序需要注意的问题
- linux sha256检验
- JNDI 是什么
- Hadoop数据库性能调优指南
- activity切换无动画效果的实现
- Maven使用心得
- SQLiteDatabase 多线程访问需要注意的问题
- 2014年苹果ios开发者证书申请及xcode5应用上线发布
- 尚学堂
- html和htm的区别
- SDUT_2015寒假集训_背包_G-小P寻宝记——好基友一起走
- Bullet(Cocos2dx)之交叉编译Android,集成到cocos2dx3.x
- 尝试加载 Oracle 客户端库时引发 BadImageFormatException
- 数据结构专题——队列
- 数据结构专题——栈