本文微信公众号「AndroidTraveler」首发。html
背景
本文是对一篇英文文档的翻译,原文请见文末连接。java
<hr/>android
并发数据库访问
假设你实现了本身的 SQLiteOpenHelper。git
public class DatabaseHelper extends SQLiteOpenHelper { ... }
如今你想要在多个线程中对数据库写入数据。github
// 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 中发现下面信息,而且你的其中一个改变不会写入数据库:sql
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
因为咱们只使用了一个数据库链接,Thread1 和 Thread2 的 getDatabase()
方法都会返回同一个 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 的时候,咱们关闭数据库链接。
如今你可以使用你的数据库而且确保是线程安全的。
<hr/>
因为本人翻译水平有限,若是你有更好的翻译文案,欢迎在 GitHub 提 PR。
这边建了一个仓库,欢迎提 PR 投稿一些好的文章翻译。
并发数据库访问
原文出处:https://www.cnblogs.com/nesger/p/11038248.html