簡體   English   中英

在多線程應用程序Android中訪問數據庫的最佳方法?

[英]Best way to access database in multi threaded application Android?

注意:請不要將此問題標記為重復。 我已經經歷了幾個類似的問題,但找不到滿意的答案。

我一直在研究使用Sqlite數據庫的應用程序。 我們遵循單例模式,確保我們只能在整個應用程序中創建一個輔助類實例。

public class CustomSqliteHelper extends SQLiteOpenHelper {

  public static CustomSqliteHelper getInstance(Context context) {
    if (instance == null) {
      synchronized (CustomSqliteHelper.class) {
        if (instance == null) {
          instance = new CustomSqliteHelper(context);
        }
      }
    }
    return instance;
  }
}

但有時應用程序崩潰與SQLiteDatabaseLockedException 我理解當多個線程/進程嘗試一次寫入數據庫時​​會出現此異常。 即使一個線程/進程在寫入操作仍在進行時嘗試讀取數據庫,也會拋出此異常。

所以我一直在閱讀很多關於這個以及防止這種情況發生的可能方法。 很多帖子建議使用ContentProvider而不是直接擴展SqliteOpenHelper類並對數據庫對象執行操作。 在閱讀其中一篇帖子時,這篇文章提到在使用Content Provider時,您不需要手動處理多線程環境。

盡管ContentProvider缺乏線程安全性,但通常您會發現在防止潛在的競爭條件方面您無需采取進一步行動。 規范示例是您的ContentProvider由SQLiteDatabase支持的時間; 當兩個線程同時嘗試寫入數據庫時​​,SQLiteDatabase將自行鎖定,確保一個等待直到另一個完成。 每個線程都將獲得對數據源的互斥訪問,從而確保滿足線程安全性。

以上引用似乎令人困惑,因為首先它提到ContentProvider不支持線程安全。 但他總結說,應用程序開發人員不需要做任何事情來實現並發。

另外,如果我選擇使用SqliteOpenHelper,那么防止這些崩潰的最佳方法是什么? 我一直在考慮為每個db操作使用鎖。

 public class CustomSqliteHelper extends SQLiteOpenHelper {

 private String lock = "lock";

 public void insert(){
    synchronized(lock){
       // Do the insert operation here.
    }
 }


 public void update(){
    synchronized(lock){
       // Do the update operation here.
    }
 }
 }

但我的一個團隊成員建議我不要這樣做,因為Java鎖很貴。

在瀏覽了Github上最受歡迎的項目之一后,我發現開發人員建議將每個數據庫操作包裝在一個事務中。

public void insert(ContentValues values) {
    // Create and/or open the database for writing
    SQLiteDatabase db = getWritableDatabase();

    // It's a good idea to wrap our insert in a transaction. This helps with performance and ensures
    // consistency of the database.
    db.beginTransaction();
    try {
        // The user might already exist in the database (i.e. the same user created multiple posts).

        db.insertOrThrow(TABLE_POSTS, null, values);
        db.setTransactionSuccessful();
    } catch (Exception e) {
        Log.d(TAG, "Error while trying to add post to database");
    } finally {
        db.endTransaction();
    }
}

我真的不確定這是否可以阻止Lock異常? 這似乎更像是一個以績效為導向的步驟。

所以在閱讀完所有博客和教程之后,我仍然感到困惑。 我的主要問題是

  1. 鑒於我的應用程序不與其他應用程序共享數據,使用ContentProviders或擴展SqliteOpenHelper是更好的選擇。
  2. 將Java鎖定放在所有操作上是最好的方法還是有比這更好的其他方法?

更新:根據@Mustanar的回答,似乎SQLiteDatabase負責鎖定機制。 這意味着如果您正在執行寫操作,則數據庫將被鎖定。 但同時,如果某個其他線程嘗試執行寫操作,那么第二個操作是否會等待直到鎖被釋放或者是否會拋出android.sqlite.database.SQLiteDatabaseLockedException異常?

更新2 :給這個問題一個賞金,因為答案似乎仍然不清楚。 我只使用Helper類的一個實例。 但仍然得到這個錯誤。

PS:感謝您提出這么長的問題。

使用Singleton模式來實例化SQLiteOpenHelper ,因此在整個應用程序中應該存在一個單例實例。 這將確保不會發生泄漏,並且可以使您的生活更輕松,因為它消除了在編碼時忘記關閉數據庫的可能性。 它還將確保在整個應用程序中安全訪問數據庫。

而且,您不必實現自己的鎖定機制。 SQLiteDatabase維護鎖定機制。 因此,在特定時間只有一個事務發生,所有其他事務將使用Sigleton.getInstance()方法在Queue 確保數據庫的單個訪問點。

此外,在此方法中, 您不必關閉數據庫連接,因為SQLiteDatabase將在事務完成后釋放所有引用。 因此,只要您想執行CRUD操作並刪除鎖定mecansim,就應該使用CustomSQLiteHelper.getInstance()

更多信息,請訪問博客http://www.androiddesignpatterns.com/2012/05/correctly-managing-your-sqlite-database.html並查看評論。

希望這可以幫助。

查詢1(單例):

你能改變下面的雙鎖定代碼來創建一個真正的Singleton對象嗎?

public class CustomSqliteHelper extends SQLiteOpenHelper {
      private CustomSqliteHelper instance;
      public static CustomSqliteHelper getInstance(Context context) {
        if (instance == null) {
          synchronized (CustomSqliteHelper.class) {
            if (instance == null) {
              instance = new CustomSqliteHelper(context);
            }
          }
        }
        return instance;
      }
    }

看看這篇文章 ,了解雙重鎖定的問題。

看看相關的SE問題:

在Java中實現單例模式的有效方法是什么?

我更喜歡創建Singleton

 private static final CustomSqliteHelper instance = new CustomSqliteHelper ();

其他方法:

enum CustomSqliteHelper {
    INSTANCE;
}

如果你還需要懶惰的Singleton,還有一種方法。

在這個SE問題中看看@ Nathan Hughes回答:

單件工廠中的同步和鎖定

查詢2 :(鎖定)

使用java鎖定可以提高應用程序的一致性。 一致性勝過費用。

SQLiteDatabaseLockedException頁面,

如果數據庫引擎無法獲取數據庫鎖,則需要執行其工作。 如果語句是[COMMIT]或發生在顯式事務之外,則可以重試該語句。 如果該語句不是[COMMIT]並且在顯式事務中發生,那么您應該在繼續之前回滾事務。

我希望您應該遵循上面的建議以及您在問題(Github)項目中發布的代碼片段。 我喜歡這個想法,上面的建議與Github示例代碼一致。

我肯定會建議您將數據庫包裝在ContentProvider

它提供了開箱即用的好功能,並將許多潛在的棘手任務轉移到框架中。 您不需要處理單例數據庫幫助程序,因為您可以保證您的應用程序有一個ContentResolver實例,可以將命令轉發給目標提供程序。

您可以使用整個Loader框架和漂亮的幫助程序類(如AsyncQueryHandler來處理一些最常見的數據庫訪問警告。

我建議不要同步對CRUD方法的訪問。 正如文檔和之前的答案中所提到的,這應該由數據庫本身來處理。 更不用說在需要密集數據庫使用的應用程序中潛在的性能損失。

旁注:作為第一步,如果您嘗試找出導致這些崩潰的原因可能會有用 - 它們是否零星? 一個更具體的例子可能在故障排除時有用。

您可以使用內容提供商。

我認為在大多數情況下使用讀寫鎖就足夠了。 看到這個答案 該解決方案不使用ContentProvider 相反,它在java.util.concurrent.locks包中使用ReentrantReadWriteLock類。

  1. 鑒於我的應用程序不與其他應用程序共享數據,使用ContentProviders或擴展SqliteOpenHelper是更好的選擇。

我想如果你不共享任何數據,擴展SqliteOpenHelper是更好的選擇。 這主要是因為它比ContentProvider更加開發友好的API :-)還有官方文檔中描述的其他內容

確定您是否需要內容提供商。 如果要提供以下一項或多項功能,則需要構建內容提供程序:

  • 您希望向其他應用程序提供復雜的數據或文件。
  • 您希望允許用戶將應用中的復雜數據復制到其他應用中。
  • 您希望使用搜索框架提供自定義搜索建議。

所以在你的情況下 - 它是多余的


對於單例初始化 - 有時你不需要它進行惰性初始化:

public class CustomSqliteHelper {
    private static final CustomSqliteHelper instance = new CustomSqliteHelper(YourApplication.getContext());

    public static CustomSqliteHelper getInstance() {
        return instance;
    }

    // all the super staff below :)
}

但是您需要在應用程序類中公開應用程序上下文:

public class YourApplication extends Application {
    private static final YourApplication instance;

    @Override
    public void onCreate() {
        super.onCreate();
        instance = this;
    }

    public static Context getContext() {
        return instance.getApplicationContext();
    }
}

加上Manifest中的配置。 這樣可以避免在創建過程中同步。


如果你真的需要這個單例進行延遲初始化 - 比我說雙重檢查鎖定 (DCL)是非常糟糕的做法。 對於安全的延遲初始化,您應該使用以下模式:

public class CustomSqliteHelper {
    private static class InstanceHolder {
        public static CustomSqliteHelper instance = new CustomSqliteHelper(YourApplication.getContext());
    }

    public CustomSqliteHelper getInstance() {
        return InstanceHolder.instance;
    }

    // all the super staff below :)
}

  1. 將Java鎖定放在所有操作上是最好的方法還是有比這更好的其他方法? 這提供安全的延遲初始化而不鎖定任何東西。

沒有必要進行任何鎖定(直到您的DAO無狀態)。 檢查SQLiteDatabase的文檔

除了確保SQLiteOpenHelper是單例之外,如果希望多個線程訪問數據庫,還需要啟用預寫日志記錄:

https://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.html#enableWriteAheadLogging()

這樣做可以解決我的問題。

正如在注釋中所討論的,如果您使用一個數據庫實例,則不會出現阻塞鎖定。 所以我懷疑DeadLock是因為你正在使用交易。

一種DeadLock示例是:

  1. 在一次交易中:

    更新表 - >更新B表 - >提交

  2. 在其他交易中:

    更新B表 - >更新表 - >提交

所以在中間時間,1等待2和2同時等待1,所以死鎖發生。

如果它確實是死鎖,您可以修復查詢以不發生這種情況,或者您可以使用像Actor這樣的排隊查詢(或異步查詢)。

例如,commons-dbutils庫支持AsyncQueryRunner,它提供ExecutorService並返回Future。 (來自: 是否可以進行異步jdbc調用?

要防止鎖定錯誤,請在每個連接中將忙碌超時設置為20秒左右。 然后,只有當您的應用程序掛起運行的事務超過該事務時,才會獲得異常。

您還可以使用來自所有線程的單個SQLiteDatabase對象,但是您必須在所有事務周圍手動鎖定它。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM