2012-10-06 34 views
10

我使用這個類來管理連接到底層SQLiteDatabaseSQLiteDatabase多線程鎖定模式

public class BasicDataSource { 

    protected DatabaseHandler dbHelper; 
    protected volatile SQLiteDatabase readable_database; 
    protected volatile SQLiteDatabase writable_database; 
    protected Object read_lock = new Object(); 
    protected Object write_lock = new Object(); 
    protected Context context; 

    protected BasicDataSource(Context ctx) { 
     dbHelper = DatabaseHandler.getInstance(ctx); 
     getReadableDatabase(); 
     dbHelper.onCreate(getWritableDatabase()); 
     this.context = ctx; 
    } 

    public synchronized void close() { 
     dbHelper.close(); 
    } 

    protected void closeInsertHelpers(InsertHelper... helpers) { 
     for (InsertHelper ih : helpers) { 
      if (ih != null) 
       ih.close(); 
     } 
    } 

    protected SQLiteDatabase getReadableDatabase() { 
     synchronized (read_lock) { 
      if (readable_database == null || !readable_database.isOpen()) { 
       readable_database = dbHelper.getReadableDatabase(); 
      } 
      return readable_database; 
     } 
    } 

    protected SQLiteDatabase getWritableDatabase() { 
     synchronized (write_lock) { 
      if (writable_database == null || !writable_database.isOpen()) { 
       writable_database = dbHelper.getWritableDatabase(); 
      } 
      return writable_database; 
     } 
    } 

    protected synchronized void open() throws SQLException { 
     getReadableDatabase(); 
     getWritableDatabase(); 
    } 
} 

它包含了兩個鎖,一個用於讀取,第二次寫。 但我還是偶爾會收到這種異常:

java.lang.RuntimeException: An error occured while executing doInBackground() 
     at android.os.AsyncTask$3.done(AsyncTask.java:299) 
     at java.util.concurrent.FutureTask$Sync.innerSetException(FutureTask.java:273) 
     at java.util.concurrent.FutureTask.setException(FutureTask.java:124) 
     at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:307) 
     at java.util.concurrent.FutureTask.run(FutureTask.java:137) 
     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1076) 
     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:569) 
     at java.lang.Thread.run(Thread.java:856) 
Caused by: android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5): , while compiling: PRAGMA journal_mode 
     at android.database.sqlite.SQLiteConnection.nativePrepareStatement(Native Method) 
     at android.database.sqlite.SQLiteConnection.acquirePreparedStatement(SQLiteConnection.java:882) 
     at android.database.sqlite.SQLiteConnection.executeForString(SQLiteConnection.java:627) 
     at android.database.sqlite.SQLiteConnection.setJournalMode(SQLiteConnection.java:313) 
     at android.database.sqlite.SQLiteConnection.setWalModeFromConfiguration(SQLiteConnection.java:287) 
     at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:215) 
     at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:193) 
     at android.database.sqlite.SQLiteConnectionPool.openConnectionLocked(SQLiteConnectionPool.java:463) 
     at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:185) 
     at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:177) 
     at android.database.sqlite.SQLiteDatabase.openInner(SQLiteDatabase.java:804) 
     at android.database.sqlite.SQLiteDatabase.open(SQLiteDatabase.java:789) 
     at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:694) 
     at android.app.ContextImpl.openOrCreateDatabase(ContextImpl.java:804) 
     at android.content.ContextWrapper.openOrCreateDatabase(ContextWrapper.java:221) 
     at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:224) 
     at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:164) 
     at com.mycompany.myapplication.sql.BasicDataSource.getWritableDatabase(BasicDataSource.java:57) 
     at com.mycompany.myapplication.sql.datasources.SomeDataSource.fillUpDatabaseMethod(SomeDataSource.java:264) 
     at com.mycompany.myapplication.sql.datasources.SomeDataSource.renewCacheMethod(SomeDataSource.java:560) 
     at com.mycompany.myapplication.activities.lists.ListsActivity$Worker.doInBackground(ListsActivity.java:315) 
     at com.mycompany.myapplication.activities.lists.ListsActivity$Worker.doInBackground(ListsActivity.java:1) 
     at android.os.AsyncTask$2.call(AsyncTask.java:287) 
     at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305) 
     ... 4 more 
android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5): , while compiling: PRAGMA journal_mode 
     at android.database.sqlite.SQLiteConnection.nativePrepareStatement(Native Method) 
     at android.database.sqlite.SQLiteConnection.acquirePreparedStatement(SQLiteConnection.java:882) 
     at android.database.sqlite.SQLiteConnection.executeForString(SQLiteConnection.java:627) 
     at android.database.sqlite.SQLiteConnection.setJournalMode(SQLiteConnection.java:313) 
     at android.database.sqlite.SQLiteConnection.setWalModeFromConfiguration(SQLiteConnection.java:287) 
     at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:215) 
     at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:193) 
     at android.database.sqlite.SQLiteConnectionPool.openConnectionLocked(SQLiteConnectionPool.java:463) 
     at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:185) 
     at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:177) 
     at android.database.sqlite.SQLiteDatabase.openInner(SQLiteDatabase.java:804) 
     at android.database.sqlite.SQLiteDatabase.open(SQLiteDatabase.java:789) 
     at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:694) 
     at android.app.ContextImpl.openOrCreateDatabase(ContextImpl.java:804) 
     at android.content.ContextWrapper.openOrCreateDatabase(ContextWrapper.java:221) 
     at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:224) 
     at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:164) 
     at com.mycompany.myapplication.sql.BasicDataSource.getWritableDatabase(BasicDataSource.java:57) 
     at com.mycompany.myapplication.sql.datasources.SomeDataSource.fillUpDatabaseMethod(SomeDataSource.java:264) 
     at com.mycompany.myapplication.sql.datasources.SomeDataSource.renewCacheMethod(SomeDataSource.java:560) 
     at com.mycompany.myapplication.activities.lists.ListsActivity$Worker.doInBackground(ListsActivity.java:315) 
     at com.mycompany.myapplication.activities.lists.ListsActivity$Worker.doInBackground(ListsActivity.java:1) 
     at android.os.AsyncTask$2.call(AsyncTask.java:287) 
     at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305) 
     at java.util.concurrent.FutureTask.run(FutureTask.java:137) 
     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1076) 
     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:569) 
     at java.lang.Thread.run(Thread.java:856) 

這意味着,試圖getWritableDatabase將收購鎖當數據庫被莫名其妙地鎖定。

我的SQLiteOpenHelper是單例模式,DataSources僅使用BasicDataSource作爲父類。

我可以做些什麼來改進顯示代碼中的SQLiteDatabaseLockedException?

回答

16

在SQLite中,可以有任意多個讀者,但任何作者都會阻止所有其他讀者和作者。

您必須爲讀者和作者使用單個鎖。

請注意,只要您實際訪問數據庫,就必須鎖定鎖定。


如果你想支持多個閱讀器,使用實現ReadWriteLock鎖,如ReentrantReadWriteLock。事情是這樣的:

class MyData { 
    private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); 
    private final Lock r = rwl.readLock(); 
    private final Lock w = rwl.writeLock(); 

    public Data ReadSomething(int id) { 
     r.lock(); 
     try { 
      Cursor c = readableDatabase.query(...); 
      return c.getString(0); 
     } finally { 
      r.unlock(); 
     } 
    } 

    public void ChangeSomething(int id, int value) { 
     w.lock(); 
     try { 
      writeableDatabase.update(...); 
     } finally { 
      w.unlock(); 
     } 
    } 
} 
+0

在多線程的概念,如果非要用這個R/W鎖定模式多線程/寫不會被處理的併發讀書,不是嗎?我的意思是,當兩個DataSources從兩個線程寫入時,此鎖定將僅對每個DS有效... –

+0

鎖定的目的是協調多個線程。所以所有的線程都會共享相同的鎖(在這個例子中,MyData類)。 –

+0

我覺得不需要鎖,因爲SQLiteDatabase在內部處理鎖定。我複雜的多線程應用程序不需要任何你描述的機制。寫入鎖定由SQLiteDatabase.beginTransaction/endTransaction處理。另外,爲什麼使用Lock類而不是同步java關鍵字? –