To record how threads securely access your Android database instance, I wrote this little note. Click here for the project code referenced in the article
Suppose you have compiled your own SQLiteOpenHelper.
public class DatabaseHelper extends SQLiteOpenHelper { ... }
Now you want to write data to the database in different threads:
// Thread 1 Context context = getApplicationContext(); DatabaseHelper helper = new DatabaseHelper(context); SQLiteDatabase database = helper.getWritableDatabase(); database.insert(…); database.close(); // Thread 2 Context context = getApplicationContext(); DatabaseHelper helper = new DatabaseHelper(context); SQLiteDatabase database = helper.getWritableDatabase(); database.insert(…); database.close();
Then, the log information similar to the following will be output in your Logcat, and your write data operation will be invalid.
android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)
The above problem occurs because every time you create a SQLiteOpenHelper object, you are actually creating a database connection. If you try to write data to the database through multiple connections at the same time, it will certainly fail.
To ensure that we can operate databases securely in multiple threads, we need to ensure that only one database connection is occupied.
First, we compile a single DatabaseManager that manages a single SQLiteOpenHelper object.
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 mDatabaseHelper.getWritableDatabase(); }}
To write data in multiple threads, modify the Code as follows:
// 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();
Then another crash occurs.
java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase
Since we only have one database connection, the call to the getDatabase () method of Thread1 and Thread2 will obtain the same SQLiteDatabase object instance. The next thing is that when Thread1 tries to manage the database connection, Thread2 is still using the database connection. This is the cause of the failure of IllegalStateException.
Therefore, we can only disable the database when it is no longer in use. We recommend "never close" Your SQLiteDatabase in stackoveflow. If you do this, the following information will appear in your logcat, so I don't think this is a good idea.
Leak foundCaused by: java. lang. IllegalStateException: SQLiteDatabase created and never closed
Example:
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(); } }}
Then how can you call it:
SQLiteDatabase database = DatabaseManager.getInstance().openDatabase();database.insert(...);// database.close(); Don't close it directly!DatabaseManager.getInstance().closeDatabase(); // correct way
In the future, whenever you need to use a database connection, you can call the DatabaseManager-class method openDatabase (). In the method, there is a built-in counter that indicates how many times the database is opened. If the count is 1, we need to open a new database connection. Otherwise, the database connection already exists.
The same is true in method closeDatabase. Every time we call the closeDatabase () method, the counter will decrease until the count is 0, and we need to close the database connection.
Tip: You should use AtomicInteger to handle concurrency
Now you can use your database connection in a thread-safe manner.
Original article: https://github.com/dmytrodanylyk/dmytrodanylyk/blob/gh-pages/articles/Concurrent%20Database%20Access.md