本文微信公衆號「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