首页 > 代码库 > Android多线程下安全访问数据库(Concurrent Database Access)

Android多线程下安全访问数据库(Concurrent Database Access)

Assuming you have your own SQLiteOpenHelper:  

1 public class DatabaseHelper extends SQLiteOpenHelper { ... }

 

Now you want to write data to database in separate threads:

 1 // Thread 1 2  Context context = getApplicationContext(); 3  DatabaseHelper helper = new DatabaseHelper(context); 4  SQLiteDatabase database = helper.getWritableDatabase(); 5  database.insert(…); 6  database.close(); 7  8  // Thread 2 9  Context context = getApplicationContext();10  DatabaseHelper helper = new DatabaseHelper(context);11  SQLiteDatabase database = helper.getWritableDatabase();12  database.insert(…);13  database.close();

 

You will get following message in your logcat and one of your changes will not be written:

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

This is happening because every time you create new SQLiteOpenHelper object you are actually making new database connection. If you try to write to the database from actual distinct connections at the same time, one will fail.

To use database with multiple threads we need to make sure we are using one database connection.

Let’s make singleton class DatabaseManager which will hold and return single SQLiteOpenHelper object.

 1 public class DatabaseManager { 2  3     private static DatabaseManager instance; 4     private static SQLiteOpenHelper mDatabaseHelper; 5  6     public static synchronized void initializeInstance(SQLiteOpenHelper helper) { 7         if (instance == null) { 8             instance = new DatabaseManager(); 9             mDatabaseHelper = helper;10         }11     }12 13     public static synchronized DatabaseManager getInstance() {14         if (instance == null) {15             throw new IllegalStateException(DatabaseManager.class.getSimpleName() +16                     " is not initialized, call initialize(..) method first.");17         }18 19         return instance;20     }21 22     public synchronized SQLiteDatabase getDatabase() {23         return mDatabaseHelper.getWritableDatabase();24     }25 26 }
Updated code which write data to database in separate threads will look like this.
 1  // In your application class 2  DatabaseManager.initializeInstance(new DatabaseHelper()); 3  4  // Thread 1 5  DatabaseManager manager = DatabaseManager.getInstance(); 6  SQLiteDatabase database = manager.getDatabase() 7  database.insert(…); 8  database.close(); 9 10  // Thread 211  DatabaseManager manager = DatabaseManager.getInstance();12  SQLiteDatabase database = manager.getDatabase()13  database.insert(…);14  database.close();

This will bring you another crash:

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

Since we are using only one database connection, method getDatabase() return same instance of SQLiteDatabaseobject for Thread1 and Thread2. What is happening, Thread1 may close database, while Thread2 is still using it. That’s why we have IllegalStateException crash.

We need to make sure no-one is using database and only then close it. Some folks on stackoveflow recommended to never close your SQLiteDatabase. This will honor you with following logcat message. So I don‘t think this is good idea at all.

Leak foundCaused by: java.lang.IllegalStateException: SQLiteDatabase created and never closed

Working sample:

 1 public class DatabaseManager { 2  3     private AtomicInteger mOpenCounter = new AtomicInteger(); 4  5     private static DatabaseManager instance; 6     private static SQLiteOpenHelper mDatabaseHelper; 7     private SQLiteDatabase mDatabase; 8  9     public static synchronized void initializeInstance(SQLiteOpenHelper helper) {10         if (instance == null) {11             instance = new DatabaseManager();12             mDatabaseHelper = helper;13         }14     }15 16     public static synchronized DatabaseManager getInstance() {17         if (instance == null) {18             throw new IllegalStateException(DatabaseManager.class.getSimpleName() +19                     " is not initialized, call initializeInstance(..) method first.");20         }21 22         return instance;23     }24 25     public synchronized SQLiteDatabase openDatabase() {26         if(mOpenCounter.incrementAndGet() == 1) {27             // Opening new database28             mDatabase = mDatabaseHelper.getWritableDatabase();29         }30         return mDatabase;31     }32 33     public synchronized void closeDatabase() {34         if(mOpenCounter.decrementAndGet() == 0) {35             // Closing database36             mDatabase.close();37 38         }39     }40 }

And use it as follows.

1 SQLiteDatabase database = DatabaseManager.getInstance().openDatabase();2 database.insert(...);3 // database.close(); Don‘t close it directly!4 DatabaseManager.getInstance().closeDatabase(); // correct way

Every time you need database you should call openDatabase() method of DatabaseManager class. Inside this method, we have a counter, which indicate how many times database is opened. If it equals to one, it means we need to create new database, if not, database is already created.

The same happens in closeDatabase() method. Every time we call this method, counter is decreased, whenever it goes to zero, we are closing database.


Now you should be able to use your database and be sure - it‘s thread safe.

 

from:github

 

Android多线程下安全访问数据库(Concurrent Database Access)