Concurrent database access


SQLite is a common practice for Android Dev, and it makes us wonder if SQLite is thread-safe compared to the console explosion you’ve probably experienced.

OK nonsense not much say, we ⬇️

Just start


First, suppose you have implemented an SQLiteHelper class like this:

public class DatabaseHelper extends SQLiteOpenHelper {... }Copy the code

Now you want to write some data to SQLite in two child threads:

 // Thread 1
 Context context = getApplicationContext();
 DatabaseHelper helper = newDatabaseHelper(context); SQLiteDatabase database = helper.getWritableDatabase(); The database insert (...). ; database.close();// Thread 2
 Context context = getApplicationContext();
 DatabaseHelper helper = newDatabaseHelper(context); SQLiteDatabase database = helper.getWritableDatabase(); The database insert (...). ; database.close();Copy the code

Isn’t it? It looks fine. Nothing wrong with it.

In this case, we click run, gio, and you will receive the following gift “error” in your logcat:

android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)
Copy the code

What’s going on?

We analyzed the error and found that it was caused by a link to the database every time you created SQLiteHelper. At this point, if you try to write to the database from actually different links at the same time, you’re bound to fail.

To sum up, if we want to do anything with the database, including reading and writing, on different threads, we have to make sure that we’re using the same connection

Ok, so now the question is clear. Now let’s create a singleton schema class: DatabaseManager to create and return unique, singleton DatabaseManager objects.

Ps Some students asked me what singletons are, so I wrote this blog post to explain singletons – globally available context objects. Is that enough

public class DatabaseManager {

    private static DatabaseManager instance;
    private static SQLiteOpenHelper mDatabaseHelper;

    public static synchronized void initializeInstance(SQLiteOpenHelper helper) {
        if (instance == null) {
            instance = newDatabaseManager(); mDatabaseHelper = helper; }}public static synchronized DatabaseManager getInstance(a) {
        if (instance == null) {
            throw new IllegalStateException(DatabaseManager.class.getSimpleName() +
                    " is not initialized, call initialize(..) method first.");
        }

        return instance;
    }

    public synchronized SQLiteDatabase getDatabase(a) {
        returnmDatabaseHelper.getWritableDatabase(); }}Copy the code

Now, let’s go back and modify the previous code. The result is as follows:

// In your application class
DatabaseManager.initializeInstance(new DatabaseHelper());

// 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();Copy the code

The logic is clearer and there is less code redundancy. Now that we’re running down the code, we get another cache:

java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase
Copy the code

Don’t panic, let’s take a closer look at the error, and we find: The singleton ensures that only one SQLiteHelper object is available for Thread 1 and Thread 2, but the problem is that when we finish running Thread 1, our database.close(); The connection to the database has been closed for us, but in the meantime our Thread 2, “Thread 2,” still keeps the reference to SQLiteHelper. For this reason, we received an IllegalStateException.

So we need to make sure that SQLiteHelper is disconnected when no one else is using it.

Make sure SQLIiteHelper is disconnected only when no one is using it

The solution to this problem has been advised by many stackoveFlow people: never disconnect SQLiteHelper, but instead you get the following output on logcat:

Leak found
Caused by: java.lang.IllegalStateException: SQLiteDatabase created and never closed
Copy the code

So, I strongly advise against using this method. To solve this problem, we introduce the concept of a counter

The standard sample

You will have a counter that perfectly solves the problem of opening/closing a database connection:

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 = newDatabaseManager(); mDatabaseHelper = helper; }}public static synchronized DatabaseManager getInstance(a) {
        if (instance == null) {
            throw new IllegalStateException(DatabaseManager.class.getSimpleName() +
                    " is not initialized, call initializeInstance(..) method first.");
        }

        return instance;
    }

    public synchronized SQLiteDatabase openDatabase(a) {
        if(mOpenCounter.incrementAndGet() == 1) {
            // Opening new database
            mDatabase = mDatabaseHelper.getWritableDatabase();
        }
        return mDatabase;
    }

    public synchronized void closeDatabase(a) {
        if(mOpenCounter.decrementAndGet() == 0) {
            // Closing databasemDatabase.close(); }}}Copy the code

We can use it in threads like this:

SQLiteDatabase database = DatabaseManager.getInstance().openDatabase(); database.insert(...) ;// database.close(); Don't close it directly!
DatabaseManager.getInstance().closeDatabase(); // correct way
Copy the code

Whenever you need to use a database, you simply call the openDatabase() method in DatabaseManager. In this method, we have a mOpenCounter object that records how many times the database has been “opened”. When it is equal to 1, it means that you need to create a new database connection to use the database, otherwise, the database is already in use.

The same happens with the closeDatabase() method, where our mOpenCounter object is decrement by one each time you call it. When it drops to zero, we close the connection to the database.

Perfect, and finally:

  1. Now you can use your database however you want, and you can trust it to be thread-safe!
  2. Of course, many students on the use of the database, there are a lot of doubts, I will later for the use of the database, make a series of summaries, interested in _Yuanhao can continue to pay attention to the programming world

Related articles


Everyone has to learn picture compression to effectively solve the Android app OOM

Android gives your Room a free ride on the RxJava bandwagon from repetitive code

The founder of the ViewModel and ViewModelProvider. Factory: the ViewModel

Singleton pattern – globally available Context objects, enough for this article

Scale gesture ScaleGestureDetector source code analysis, this article is enough

ObjectAnimator, ValueAnimator, ObjectAnimator, ValueAnimator

After watching this animation frame of Never View again, I kneeled and rubbed the clothes board

Android custom clock control hour, minute hand, second hand drawing this article is enough

Android custom control – draw clock dial

Android Advanced custom ViewGroup Custom layout

Welcome to attention_yuanhaoThe nuggets!


Regularly share Android development wet goods, the pursuit of humor and depth of the article perfect unity.

About the source Demo link: in order to write Demo spent several days, I hope you order songs star~ thank you!

Thumb up, please! Because your encouragement is the biggest power that I write!