Sto cercando di ottenere un modello che non fallisce per un accesso multithread al mio database SQLite. Inoltre, quello che mi sta facendo impazzire è che non riesco a riprodurre il problema.Database SQLite, multithreading, serrature e sincronizzazione dell'account su Android
Ho un'app che utilizza un DB, ma anche account Android e sincronizzazione Android per sincronizzare i dati della mia app. La mia ipotesi è che quando i due accadono contemporaneamente, si blocca. Sto ricevendo un sacco di errori come:
* android.database.sqlite.SQLiteDatabaseLockedException: database is locked
* android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)
* android.database.sqlite.SQLiteDatabaseLockedException: error code 5: database is locked
* android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5): , while compiling: PRAGMA journal_mode
* android.database.sqlite.SQLiteDiskIOException: disk I/O error (code 778)
* android.database.sqlite.SQLiteException: Failed to change locale for db '/data/data/net.bicou.redmine/databases/redmine.db' to 'en_US'. \n Caused by: android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)
Forse non tutti sono legati alla stessa causa principale, tuttavia Sono un po 'perso.
Quello che ho è:
- una classe base astratta,
DbAdapter
, che si estende dalle sottoclassi che vogliono gestire una singola tabella - una classe che gestisce il database SQLite, chiamato
DbManager
, che contiene aLock
In questo momento gli utenti dispongono di una versione di DbManager
che non è un singleton. Sto pianificando di rendere DbManager
un singleton, in modo che tutti i thread condividano lo stesso oggetto. Questo non dovrebbe essere un problema, perché per quanto ho capito/visto, la sincronizzazione in background e l'app condividono lo stesso processo.
Ecco le classi (solo le parti rilevanti):
public abstract class DbAdapter {
Context mContext;
protected DbManager mDbManager;
SQLiteDatabase mDb;
public static final String KEY_ROWID = "_id";
public DbAdapter(final Context ctx) {
mContext = ctx;
}
public DbAdapter(final DbAdapter other) {
mContext = other.mContext;
mDb = other.mDb;
mDbManager = other.mDbManager; // removed with singleton version
}
public synchronized DbAdapter open() throws SQLException {
if (mDb != null) {
return this;
}
mDbManager = new DbManager(mContext); // currently in production
mDbManager = DbManager.instance(mContext); // currently investigating this singleton solution
try {
mDb = mDbManager.getWritableDatabase();
} catch (final SQLException e) {
L.e("Unable to open DB, trying again in 1 second", e);
try {
Thread.sleep(1000);
} catch (final InterruptedException e1) {
L.e("Could not wait 1 second " + e1);
}
mDb = mDbManager.getWritableDatabase();// This may crash
}
return this;
}
public synchronized void close() {
mDbManager.close();
mDbManager = null;
mDb = null;
}
}
Una classe che deve gestire una tabella di database si estenderà DbAdapter
, e implementare metodi come select
, insert
, delete
, ecc
Ecco il manager DB:
public class DbManager extends SQLiteOpenHelper {
private static final String DB_FILE = "db";
private static final int DB_VERSION = 15;
Context mContext;
Lock mLock = new ReentrantLock();
// Currently in prod
public DbManager(final Context context) {
super(context, DB_FILE, null, DB_VERSION);
mContext = context;
}
// singleton version will make this constructor private and add:
private static DbManager mInstance;
public static synchronized DbManager instance(Context context) {
if (instance == null) {
instance = new DbManager(context);
}
return instance;
}
@Override
public SQLiteDatabase getWritableDatabase() {
mLock.lock();
return super.getWritableDatabase();
}
@Override
public void close() {
super.close();
mLock.unlock();
}
@Override
public void onCreate(final SQLiteDatabase db) {
// ...
}
@Override
public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {
// ...
}
private void createTables(final SQLiteDatabase db, final String[] statements) {
for (final String sql : statements) {
try {
db.execSQL(sql);
} catch (final Exception e) {
L.e("Unable to create table: " + sql, e);
}
}
}
}
OK, ora, le domande.
- Il blocco è stato implementato correttamente? Sono davvero nuovo a questo, non so se il
ReentrantLock
è una buona scelta, e se sto bloccando/sblocco al momento giusto - Il mio metodo
synchronized
è correttamente implementato? Voglio dire, ho inserito la parola chiavesynchronized
attorno a metodi che non desidero essere interrotti da thread simultanei. È giusto? Potete consigliarmi sull'uso disynchronized
? - Come posso riprodurre il problema? Ho creato un test che utilizza 3 thread che consentono l'accesso simultaneo in lettura/scrittura al DB e utilizza alcuni
Thread.sleep
per garantire che il db open/close da ogni thread si sovrapponga, ma non si arresti in modo anomalo. Questo mi disturba davvero, non penso che ci sia un sacco di gente che ha il problema, quindi non so come riprodurlo. - La mia scelta tecnica
DbAdapter
+DbManager
è una buona idea? C'è uno schema migliore? - È una buona idea rendere
DbManager
un singleton?