关于Android SQLite3多线程并发问题

最近有看到过Sqlite3的相关文章,在这做一下学习笔记。

关于sqlite的介绍,可以参照一下:http://blog.csdn.net/ghost5216/article/details/4863246

sqlite3数据库是一个数据库一个文件,所以当多进程访问操作同一数据库时,即与操作同一文件一样,文件锁问题。
对同个数据库进行多进程同时读是允许的,但多进程同时写是不允许的,如果一个进程已经正在写,其他进程就会写失败。sqlite3返回信息就是"Database is locked",错误码SQLITE_BUSY,sqlite3对高并发访问支持不好,多线程访问数据库会出现数据库锁定现象。

那么在并发的情况下,如何安全的访问sqlite数据库呢?

假设你已经有一个自己的SQLiteOpenHelper。

[java]  view plain  copy
  1. public class DatabaseHelper extends SQLiteOpenHelper { ... }  

[java]  view plain  copy
  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();  
在你的logcat中将会得到下面的输出信息,并且有一个写操作将不会成功

[java]  view plain  copy
  1. android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)  

发生这种情况是因为你每次都创建了一个新的SQLiteOpenHelper,实际上你每次都创建了一个数据库的连接。如果你在同一时间用不同的数据库连接来对同一的数据库进行写操作的话,那么其中一个会失败。
Android 平台上,如果你想在多线程环境下安全的使用数据库的话,那么你得确保所有的线程使用的都是同一个数据库连接。

确保只有一个数据库连接存在,我们可以使用单例模式,代码如下:

[java]  view plain  copy
  1. public class DatabaseManager {  
  2.   
  3.     private static DatabaseManager instance;  
  4.     private static SQLiteOpenHelper mDatabaseHelper;  
  5.   
  6.     public static synchronized void initialize(Context context, 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 new mDatabaseHelper.getWritableDatabase();  
  24.     }  
  25.   
  26. }  

现在我们来看看执行结果:

// 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();
你的应用将会崩溃,异常信息如下:

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

因为我们只使用一个数据库连接,Thread1和Thread2的都是由getDatabase()方法返回的相同连接。发生的什么事呢,在Thread2还在使用数据库连接时,Thread1可能已经把它给关闭了,那就是为什么你会得到崩溃异常。
我们需要确保在没有任何一个人在使用数据库时,才去关闭它。在StackOverflow上推荐的做法是永远不要关闭数据库。Android会尊重你这种做法,但会给你如下的提示。所以我一点也不推荐这种做法。

Leak found
Caused by: java.lang.IllegalStateException: SQLiteDatabase created and never closed
下面是一个样例程序

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();

        }
    }}
使用方式:

SQLiteDatabase database = DatabaseManager.getInstance().openDatabase();
database.insert(...);
// database.close(); Don't close it directly!
DatabaseManager.getInstance().closeDatabase(); // correct way
每当你需要使用数据库时,你需要使用DatabaseManager的openDatabase()方法来取得数据库。我们会使用一个引用计数来判断是否要创建数据库对象。如果引用计数为1,则需要创建一个数据库,如果不为1,说明我们已经创建过了。
在closeDatabase()方法中我们同样通过判断引用计数的值,如果引用计数降为0,则说明我们需要close数据库。

大致的做法就是在多线程访问的情况下需要自己来封装一个DatabaseManager来管理Sqlite数据库的读写,需要同步的同步,需要异步的异步,不要直接操作数据库,这样很容易出现因为锁的问题导致加锁后的操作失败。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
引用\[1\]:根据官方文档,从3.3.1版本开始,SQLite支持多线程并发访问数据库,但要求设置为SQLITE_THREADSAFE模式。每个数据库连接只能在一个线程中使用,不可多个线程共用一个数据库连接。\[1\]引用\[2\]:SQLite默认情况下只支持单个数据库连接操作,多连接并发操作时会出问题,需要开启wal模式才能进行多项城并发读操作,操作仍不能并发。\[2\]根据这些信息,可以得出以下结论:在SQLite中,多线程操作数据库有两种解决方案。第一种是多线程读和,只使用一个数据库连接,这种情况下由于使用的是同一个数据库连接SQLite内部有锁机制,不会出现异常。但是这种多线程操作并不是真正的并发操作,由于锁机制的存在,仍然是阻塞的。第二种是多线程读和单线程,每个线程使用独立的数据库连接,并开启wal模式。这种情况下可以实现多线程并发读操作,但操作仍然只能单线程进行。如果多个线程同时入(每个线程使用独立的数据库连接),就会出现问题。\[2\]在实际测试中,方案一的用时是方案二的两倍多。\[2\]关于方案一的具体实现,有两种方式:一是开启一个数据库连接永不关闭,二是自己编一个管理类来实现。\[2\]要开启wal模式,可以使用SQLiteDatabase.enableWriteAheadLogging()方法。\[2\]在多线程操作SQLite时,可能会遇到数据库被锁定的问题,这可能是由于子线程执行的query函数有问题导致的。\[3\] #### 引用[.reference_title] - *1* [SQLite多线程应用与iOS开发](https://blog.csdn.net/pcliuguangtao/article/details/8123473)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [SQLite多线程并发操作](https://blog.csdn.net/a987687115/article/details/84566282)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [Android SQLite多线程读写和线程同步源码分析](https://blog.csdn.net/z979451341/article/details/79593551)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值