To record how threads securely access your Android database instance, I wrote this little note. For the project code referenced in this article, click here to assume that you have compiled your own SQLiteOpenHelper.
publicclassDatabaseHelperextendsSQLiteOpenHelper{...}
Now you want to write data to the database in different threads:
// Thread 1Context context = getApplicationContext();DatabaseHelper helper =newDatabaseHelper(context);SQLiteDatabase database = helper.getWritableDatabase(); database.insert(…); database.close();// Thread 2Context context = getApplicationContext();DatabaseHelper helper =newDatabaseHelper(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.
publicclassDatabaseManager{privatestaticDatabaseManager instance;privatestaticSQLiteOpenHelper mDatabaseHelper;publicstaticsynchronizedvoid initialize(Context context,SQLiteOpenHelper helper){if(instance ==null){ instance =newDatabaseManager(); mDatabaseHelper = helper;}}publicstaticsynchronizedDatabaseManager getInstance(){if(instance ==null){thrownewIllegalStateException(DatabaseManager.class.getSimpleName()+" is not initialized, call initialize(..) method first.");}return instance;}publicsynchronizedSQLiteDatabase getDatabase(){returnnew mDatabaseHelper.getWritableDatabase();}}
To write data in multiple threads, modify the Code as follows:
// In your application classDatabaseManager.initializeInstance(getApplicationContext());// Thread 1DatabaseManager manager =DatabaseManager.getInstance();SQLiteDatabase database = manager.getDatabase() database.insert(…); database.close();// Thread 2DatabaseManager 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:
publicclassDatabaseManager{privateAtomicInteger mOpenCounter =newAtomicInteger();privatestaticDatabaseManager instance;privatestaticSQLiteOpenHelper mDatabaseHelper;privateSQLiteDatabase mDatabase;publicstaticsynchronizedvoid initializeInstance(SQLiteOpenHelper helper){if(instance ==null){ instance =newDatabaseManager(); mDatabaseHelper = helper;}}publicstaticsynchronizedDatabaseManager getInstance(){if(instance ==null){thrownewIllegalStateException(DatabaseManager.class.getSimpleName()+" is not initialized, call initializeInstance(..) method first.");}return instance;}publicsynchronizedSQLiteDatabase openDatabase(){if(mOpenCounter.incrementAndGet()==1){// Opening new database mDatabase = mDatabaseHelper.getWritableDatabase();}return mDatabase;}publicsynchronizedvoid 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