android sqlite数据库并发问题的详细描述和解决方案

文章的开始说几句题外话,我们写程序的最重要的也是最基础的就是操作数据,也是避免不了和数据库打交道。在我的写android sqlite数据库中遇到了一些异常困扰了我很久。尝试了几种解决方案,找到种我感觉还是较为合理的解决方案。我很高兴把它写出来和大家共享,希望对你有一些帮助。
废话不多说,文章的开始先抛出异常SQLiteDatabaseLockedException: database is locked和java.lang.IllegalStateException: attempt to re-open an already-closed object.这两个是我们最常见的数据库并发异常。问题要一个一个的解决,我先说说数据库锁定出现的原因,能够明白问题出现的原因。问题解决起来就会简单的多。
我描述下场场景:如果现在有两个线程在同时的插入数据,要注意到这个里我们使用的SQLiteOpenHelper是new出来的,也就是说在不同的线程使用的是不同的helper的实例(记住这一点对你理解下面的事情会有帮助)。

线程一:
DatabaseHelper helper =newDatabaseHelper(context);
SQLiteDatabase database = helper.getWritableDatabase();
database.insert(…);
database.close();
线程二:
DatabaseHelper helper =newDatabaseHelper(context);
SQLiteDatabase database = helper.getWritableDatabase();
database.insert(…);
database.close();

如果你这么写,那么你就会得到下面的异常:

20150122113336012.png

对,你没有看错。这个就是所谓的”database is locked“异常。看到这里你不免会有些疑惑,难道这是因为我使用两个线程同步写入就会抛出这个异常吗??你这么说也可以,但我们要把问题描述清楚,这种情况只能是查看APi,api的大概意思是这样的:SQLite是文件级别的锁.SQLite3对于并发的处理机制是允许同一个进程的多个线程同时读取一个数据库,但是任何时刻只允许一个线程/进程写入数据库。在操行写操作时,数据库文件被琐定,此时任何其他读/写操作都被阻塞,如果阻塞超过5秒钟(默认是5秒,能过重新编译sqlite可以修改超时时间),就报"database is locked"错误。
你能理解最好,看不太懂也没有关系。我简单的解释一下,意思就是sqlite数据库支持不同的线程同时读取,但是在同一时间只能有一个线程在写入数据,此时有其他线程写入或者读取数据库就会报“database is locked”异常。看到这里你肯定要问了,说了这么半天问题出现嘞。怎么解决呢??先不要着急,我先把问题描述清楚以后,就能很好的理解这个错误。刚才说过我们使用数据库时是用new helper的这种形式获取对象,也就是说我们不同线程使用的是不同的helper对象,也就是不同的数据库连接。截图如下:

20150122113456859.png

对吧,线程115389和线程11540对应的是不同的helper对象,现在还有一个问题就是,刚才api上说一个线程在写入时是不能有其他线程有操作的,读取也不行吗?我也有这个疑惑。所以我测试了,结果就是“database is locked”异常与我不期而遇。。呵呵。。。。
现在问题很明白了,那怎么解决呢? 我尝试着使用单例的形式使用helper(extend SqliteOpenHelper)对象,这个我用到了单例模式,有不清楚的同学可以到这个网址查看关于单例模式的解释。WWW.blog.csdn.net/guolin_blog/article/details/8860649

/**
     * 双重锁定
     * @param context
     * @return
     */
    public  static DBHelper getInstance(Context context) {
        /**
         * 只有在为空的时候,会有同步锁的影响
         */
        if (mInstance == null) {
            synchronized (DBHelper.class) {
                if (mInstance == null) {    
                mInstance = new DBHelper(context);
                }
            }
        }  
        return mInstance;  
    };  

ps:我们在操作数据库是应该是一个数据库对应一个SQliteOpenHelper对象,因为每个数据库的操作方法不同。helper又会对应一个manager用于管理该数据库的增删改查。那么helper对象的获取应该是写到其对应的manager类中的。我将helper的对象单例化后,确保我使用不同线程使用的是同一个数据库连接。所以我把数据库操作方法改成这个样子。

线程一:
       DBManagerCorrect manager = new DBManagerLocked(MainActivity.this);
         SQLiteDatabase openDb = manager.openDb();
         manager.insertAll();
         manager.CloseDb(openDb);
线程一:
       DBManagerCorrect manager = new DBManagerLocked(MainActivity.this);
         SQLiteDatabase openDb = manager.openDb();
         manager.insertAll();
         manager.CloseDb(openDb);

这样写和原来的那种没有很大的区别,关键是manger类中的代码:

  private static DBHelper helper;  
  private static SQLiteDatabase db;  
    public DBManagerLocked(Context context) {  
        helper = DBHelper.getInstance(context);
    }  
    /**
     * @return  打开SQLiteDatabase 对象
     */
    public synchronized SQLiteDatabase openDb(){
        if(db==null){
            db=helper.getWritableDatabase();
        }
        return db;
    }
    /**
     * 关闭数据库
     * @param database 需要关闭的SQLiteDatabase对象
     */
    public synchronized void CloseDb(SQLiteDatabase database){
        if(db!=null){
            database.close();
        }
    }

我们在构建DBmanger类的对象的时候,获取helper对象的单例,每次操作数据库的时候先打开db,然后再关闭db.因为是多线程操作所以我添加为该方法添加了同步锁。(因为这篇文章主要是解决数据库的并发问题,有多同步机制不清楚的同学可以自己先找找相关文章进行阅读)。这样的写法下,我们就可以和数据库的database is locked异常say goodbay了。说到这里应该有很多朋友有相同的疑问,你现在还不是多线程同时写入,怎么就能解决问题的呢?呵呵,如果你能想到这里,说明你还是很有天赋的嘛。没错我们现在是多线程操作,但是我们的helper对象时单例的也就是说我们使用的数据库连接是同一个。看到不同了吧。

SQLiteDatabaseLockedException: database is locked 异常的解决方案:
我们明白问题的原因后 ” database is locked “的解决方法很简单就一句话, 我们将SqliteHelper对象设置为单例就可以解决。
之后的我尝试去多线程读写数据库,我的应用崩溃了。异常是这个:java.lang.IllegalStateException: attempt to re-open an already-closed object。 你应该能理解我很郁闷,改了半天还不如不改呢。但问题出现了还是要修改对吧。这个异常只要你理解多线程的机制就很好解释,因为我使用的是同一个数据库连接,所以我在操作数据库的都会调用manager的方法去打开(db=helper.getWritableDatabase())和关闭db(database.close())。这就是问题的原因,场景为:线程A打开数据,正在使用数据库,这时cpu片段分到线程B,线程A挂起。线程B进入执行获取打开db时没有问题,线程B进行操作,在片段时间内数据操作完成,最后关闭数据库database.close()。线程B执行结束,线程A执行,插入数据或者其他操作。。。我靠,怎么数据库关闭了呢,然后抛出java.lang.IllegalStateException: attempt to re-open an already-closed object异常。配图如下

20150122140153207.png

问题就是这么简单,这时有同学就笑了,原来是这样,这个不简单嘛,我给所有的数据库都添加上同步锁,让你还给我搞事。这样也可以,但是同步锁怎么说也是影响性能的,能不使用就不使用对不。那怎么整,我一直不关闭数据库嘛。还别说这个是真的可以,只不过还有更好的方式实现。我也不多说废话。

java.lang.IllegalStateException: attempt to re-open an already-closed object异常的解决方案:

  private static DBHelper helper;  
      private static SQLiteDatabase db;  
        /**
         * 记数器 应该设置静态的类变量
         * @param context
         */
        private static  int mCount;
        //同一个数据库连接
        private static DBManagerCorrect mMnanagerInstance;  
        private DBManagerCorrect(Context context) {  
            helper = DBHelper.getInstance(context);
        }  
        //单例
        public static synchronized DBManagerCorrect getIntance(Context context){
            if(mMnanagerInstance==null){
                return new DBManagerCorrect(context);
            }
            return mMnanagerInstance;
        }
        
        public synchronized SQLiteDatabase openDb(){
            if(mCount==0){
                db=helper.getWritableDatabase();
            }
            mCount++;
            return db;
        }
        public synchronized void CloseDb(SQLiteDatabase database){
            mCount--;
            if(mCount==0){
                database.close();
            }
 }

这个还是manager类中的代码,和之前代码的区别就是将该类中引入一个用于计算的静态变量mCount。这样一来我们操作数据库时调用(manager.openDb())每次打开数据连接的时候加一,每次关闭(manager.ColoseDb())的时候再减一。这样一来如果该值不减到零的时候不会关闭数据库。如果该值减到零说明没有数据库连接在使用,就可以正常关闭数据库。这样可以很轻松的避免出现上面的异常,再使用的时候也完成不需要担心数据库的关闭,只需要调用manager的这个两个函数即可。类如:

DBManagerCorrect manager=DBManagerCorrect.getIntance(MainActivity.this);
                    // step一 打开数据库
                    SQLiteDatabase openDb = manager.openDb();
                    //数据库的相关操作
                    //..........
                    //..........
                    
                    //step二 关闭数据库
                    manager.CloseDb(openDb);

说了这么多问题的原因和解决方案,我想大家应该很清楚了。如果你有好的建议,,欢迎大家在评论区提出意见。
源码链接

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容