Android多線程下安全訪問數據庫

jopen 10年前發布 | 44K 次閱讀 Android Android開發 移動開發

 // Thread 2
 Context context = getApplicationContext();
 DatabaseHelper helper = new DatabaseHelper(context);
 SQLiteDatabase database = helper.getWritableDatabase();
 database.insert(…);
 database.close();

        然后在你的Logcat中將輸出類似下面的日志信息,而你的寫數據操作將會無效。

android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)

       上面問題的出現,源于你每創建一個 SQLiteOpenHelper  對象時,實際上也是在新建一個數據庫連接。如果你嘗試通過多個連接同時對數據庫進行寫數據操作,其一定會失敗。

       為確保我們能在多線程中安全地操作數據庫,我們需要保證只有一個數據庫連接被占用。

       我們先編寫一個負責管理單個 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();
}}</pre> <p style="line-height:20px;background-color:#222222;margin-top:0px;font-family:'Helvetica Neue', Helvetica, Arial, sans-serif;white-space:normal;color:#999999;font-size:14px;">        為了能在多線程中進行寫數據操作,我們得修改一下代碼,具體如下: </p>

// 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();</pre>

        然后又導致另個崩毀

java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase

        既然我們只有一個數據庫連接,Thread1 和 Thread2 對方法 getDatabase() 的調用就會取得一樣的 SQLiteDatabase 對象實例。之后的事情就是,當 Thread1 嘗試管理數據庫連接時,Thread2 卻仍然在使用該數據庫連接。這也就是導致 IllegalStateException 崩毀的原因。

        因此我們只能在確保數據庫沒有再被占用的情況下,才去關閉它。在 stackoveflow 上有一些討論推薦“永不關閉”你的 SQLiteDatabase 。  如果你這樣做,你的logcat將會出現以下的信息,因此我不認為這是一個好主意。

Leak foundCaused 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();

    }
}}</pre> <p style="line-height:20px;background-color:#222222;margin-top:0px;font-family:'Helvetica Neue', Helvetica, Arial, sans-serif;white-space:normal;color:#999999;font-size:14px;">         然后你可以怎樣子去調用它:</p>

SQLiteDatabase database = DatabaseManager.getInstance().openDatabase();
database.insert(...);
// database.close(); 
Don't close it directly!DatabaseManager.getInstance().closeDatabase(); // correct way

         以后每當你需要使用數據庫連接,你可以通過調用類 DatabaseManager 的方法openDatabase()。在方法里面,內置一個標志數據庫被打開多少次的計數器。如果計數為1,代表我們需要打開一個新的數據庫連接,否則,數據庫連接已經存在。

在方法 closeDatabase() 中,情況也一樣。每次我們調用 closeDatabase() 方法,計數器都會遞減,直到計數為0,我們就需要關閉數據庫連接了。

         提示: 你應該使用 AtomicInteger 來處理并發的情況

         現在你可以線程安全地使用你的數據庫連接了。

         原文: https://github.com/dmytrodanylyk/dmytrodanylyk/blob/gh-pages/articles/Concurrent%20Database%20Access.md

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!