2012-10-06 19 views
10

Używam tej klasy zarządzać połączenia bazowego SQLiteDatabaseSQLiteDatabase wielowątkowego wzorzec blokujący

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(); 
    } 
} 

Zawiera dwa zamki, jeden dla odczytu, drugi do zapisu. Ale ja nadal od czasu do czasu uzyskania tego rodzaju wyjątek:

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) 

Co oznacza, że ​​baza danych jest w jakiś sposób zablokowany, gdy próbuje nabyć blokadę w getWritableDatabase.

Mój SQLiteOpenHelper jest wzorcem singletowym, a DataSources używają tylko BasicDataSource jako klasy nadrzędnej.

Jakie ulepszenie mogę zrobić, aby uniknąć SQLiteDatabaseLockedException w pokazanym kodzie?

Odpowiedz

16

W SQLite może być dowolnie wielu czytelników, ale każdy autor blokuje wszystkich innych czytelników i pisarzy.

Musisz użyć pojedynczego zamka dla czytników i autorów.

Należy pamiętać, że zamki muszą być przechowywane tak długo, jak faktycznie dostęp do bazy danych.


Jeśli chcesz obsługiwać wiele czytelników, użyj blokady, która implementuje ReadWriteLock, takiego jak ReentrantReadWriteLock. Coś takiego:

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

W koncepcji wielowątkowego, Jeśli mam wielu nici przy użyciu tego R/W wzorzec blokujący, jednoczesne czytanie/pisanie nie zostanie obsłużony, prawda? Mam na myśli, że gdy dwa Źródła Danych zapisują się z dwóch wątków, ta blokada będzie ważna tylko dla każdego pojedynczego DS ... –

+0

Celem blokady jest koordynacja wielu wątków. Więc wszystkie wątki będą miały tę samą blokadę (w tym przykładzie klasa 'MyData'). –

+0

Uważam, że żadne blokady nie są potrzebne, ponieważ SQLiteDatabase obsługuje wewnętrzne blokowanie. Moja złożona aplikacja wielowątkowa nie potrzebuje żadnego takiego mechanizmu, jak opisujesz. Blokowanie zapisu jest obsługiwane przez SQLiteDatabase.beginTransaction/endTransaction. Po co także używać klasy Lock i niezsynchronizowanego słowa kluczowego java? –