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 2
11  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 found
Caused 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 database
28             mDatabase = mDatabaseHelper.getWritableDatabase();
29         }
30         return mDatabase;
31     }
32
33     public synchronized void closeDatabase() {
34         if(mOpenCounter.decrementAndGet() == 0) {
35             // Closing database
36             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

 
时间: 2024-07-31 19:15:12

Android多线程下安全访问数据库(Concurrent Database Access)的相关文章

Android多线程操作sqlite(Sqlite解决database locked问题)

参考http://blog.csdn.net/sdsxleon/article/details/18259973  很好 https://github.com/2point0/Android-Database-Locking-Collisions-Example 示例 http://www.eoeandroid.com/forum.php?mod=viewthread&tid=333473 http://bbs.51cto.com/thread-990260-1.html 用事务,速度会很会 方

Android 使用MySQL直接访问数据库

在实际项目中,一般很少直接访问MySQL数据库,一般情况下会通过http请求将数据传送到服务端,然后在服务端连接mysql数据库. 在android 中,会通过使用Jdbc 连接MySQL 服务器 public class MySqlHelp { public static boolean InsertSql(String Bc,String lr,String rr,String TestDate) { com.yy.eye.lib.DB.MySqlSetting mySqlSetting=m

Android中Activity中访问数据库操作记录

public class MainActivity extends AppCompatActivity { String UserName = "hhh";//用户名    String Password = "137006";//密码    Connection con = null; @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(sa

无废话Android之android下junit测试框架配置、保存文件到手机内存、android下文件访问的权限、保存文件到SD卡、获取SD卡大小、使用SharedPreferences进行数据存储、使用Pull解析器操作XML文件、android下操作sqlite数据库和事务(2)

1.android下junit测试框架配置 单元测试需要在手机中进行安装测试 (1).在清单文件中manifest节点下配置如下节点 <instrumentation android:name="android.test.InstrumentationTestRunner" android:targetPackage="com.example.demo1" /> 上面targetPackage指定的包要和应用的package相同. (2)在清单文件中ap

使用FMDB多线程访问数据库,及database is locked的问题

今天终于解决了多线程同时访问数据库时,报数据库锁定的问题,错误信息是: Unknown error finalizing or resetting statement (5: database is locked) 最后通过FMDatabaseQueue解决了这个问题,本文总结一下: FMDatabase不能多线程使用同一个实例 多线程访问数据库,不能使用同一个FMDatabase的实例,否则会发生异常.如果线程使用单独的FMDatabase实例是允许的,但是同样有可能发生database is

Android 使用存放在存assets文件夹下的SQLite数据库

因为这次的项目需要自带数据,所以就就把数据都放到一个SQLite的数据库文件中了,之后把该文件放到了assets文件夹下面.一开始打算每次都从assets文件夹下面把该文件夹拷贝到手机的SD卡或者手机自身的存储上之后再使用,后来考虑到每次都拷贝的话效率不高,并且如果涉及到对数据库的修改操作的话拷贝之后数据就被恢复了. 因此就写了该封装,该封装只是在第一次使用数据库文件的时候把该文件夹拷贝到手机的/data/data/应用程序报名/database文件夹下,之后就直接从这个地方使用了.并且它允许你

使用ab.exe监测100个并发/100次请求情况下同步/异步访问数据库的性能差异

ab.exe介绍 ab.exe是apache server的一个组件,用于监测并发请求,并显示监测数据 具体使用及下载地址请参考:http://www.cnblogs.com/gossip/p/4398784.html 本文的目的 通过webapi接口模拟100个并发请求下,同步和异步访问数据库的性能差异 创建数据库及数据 --创建表结构 CREATE TABLE dbo.[Cars] ( Id INT IDENTITY(1000,1) NOT NULL, Model NVARCHAR(50) 

转发 FMDB多线程下&quot;is currently in use&quot; 或者 &quot;database is locked&quot; 问题

FMDB多线程下"is currently in use" 或者 "database is locked" 问题 问题一: "is currently in use" 出现的场景是这样的,多线程操作数据库,每个线程都使用了FMDatabase实例(注意没有使用FMDatabaseQueue). 问题二:“database is locked"出现的场景是这样的,多线程操作数据库,每个线程各自创建了FMDatabaseQueue实例操作数

Android下利用SQLite数据库实现增删改查

1: 首先介绍如何利用adb查看数据库 1: adb shell 2: cd /data/data/包名/databases 3:  sqlite3 数据库 4   接下来就可以进行数据库的sql语法的使用了 bean对象: public class Person { private int id; private String name; private String number; } 数据库的创建以及表的创建: package com.example.db; import android.