XUtils数据库模块在多线程中的打开与关闭问题

来源:互联网 发布:淘宝开店图片怎么弄 编辑:程序博客网 时间:2024/04/30 22:40
  在之前的开发中,数据库一直是用XUtils这个框架(该框架包含了数据库、网络请求、图片缓存、View管理四个部分,本文只涉及其数据库部分)来做的。总地来说,这是个不错的框架:1.它以注解的形式定义表及表字段,不需要撰写数据库语句;2.进行了高度的封装,只对用户暴露了一个通用类接口;3.屏蔽了所有的数据库语句(包括建表,增删改查)和游标管理。但是,这个XUtils这个框架是有缺点的,它是非线程安全的

  比如有两个并发的数据库操作任务:任务A和任务B;任务A和任务B的对象是同一个数据库

  任务A的逻辑是:打开数据库------>执行操作---------->关闭数据库;
  任务B的逻辑是:打开数据库------>执行操作---------->关闭数据库;

  对于以上这两个任务需要做的说明是:

  1.安卓的SQLite数据库实际上是以File的形式实现的,因此,SQLite支持并发的读操作,不支持并发的写操作;所以,抛开XUtils这个框架来看,这种并发的数据库操作若是写操作则一定是存在问题的,然而这个问题并不是导致上述错误的原因;这个问题会在后续进行讨论;本文只讨论并发的读操作。

  2.数据库必须在使用后关闭,曾看到有文章介绍“永不关闭的数据库”,这是不行的,如果你这么操作会得到一个警告,database never closed;并且这也是不符合一般认知的,一样东西,肯定是用的时候拿出来,不用的时候收起来,没有一直晾在外面的道理。

  3.问题出现在:任务A关闭了数据库时,任务B未能完成任务。打开和关闭的时机不正确就会出现以下问题:

  问题一:java.lang.IllegalStateException:attempt to re-open an already-closed object.

  问题二:java.lang.IllegalStateException: Cannot perform this operation because the connection pool has been closed.

  这两个问题是我使用XUtils框架出现的最多的两个问题,这里不再深入分析这两个问题,姑且把数据库看成是一个透明的对象,使用时需要打开,使用完需要关闭。因此,并发的数据库操作,就面临着是否需要打开,何时进行关闭的问题;

  下面需要研究下XUtils给用户暴露的接口-------DbUtils类。

  1.DbUtils定义了一个类变量-----HashMap<String, DbUtils> daoMap = new HashMap<String, DbUtils>()------用于存放“状态为打开的数据库”(key为数据库名);其实这么说并不准确,但是如此理解却是可以的;具体可看下文;

  2.作为用户,打开数据库需要调用DbUtils的create()方法,这个方法被重载了若干次,参数最完整的是

  create(Context context, String dbDir, String dbName, int dbVersion, DbUpgradeListener dbUpgradeListener)

  参数分别为:上下文,文件路径,数据库名,版本号,版本升级监听器;

  上述参数被封装在DbUtils的一个内部类(DaoConfig)对象中,create方法在最后一步调用getInstance方法,传入的参数正是Daoconfig对象;

  synchronized getInstance(DaoConfig daoConfig)

  getInstance的逻辑是先在daoMap中根据数据库名查看是否已存在该数据库,若存在则直接返回;若不存在,则调用构造方法

  DbUtils(DaoConfig config)

  构造方法则调用了

  createDatabase(DaoConfig config)

  该方法返回的是一个SQLiteDatabase对象,其方法体包含了真正的数据库创建语句----SQLiteDatabase.openOrCreateDatabase();

  3.作为用户,关闭数据库需要调用DbUtils的close()方法,在该方法中:首先检查关闭的数据库对象是否在daoMap中,若在则remove掉;然后执行数据库对象的close()方法;

  现在,回过头来看daoMap这个类变量,实际上它是保存的是“打开状态”的数据库,一旦数据库关闭,则不能在它里面找到数据库对象;如果,你把几个数据库操作任务单线程顺序调用,则从不会再daoMap中获得数据库对象;当你多线程的执行数据库操作任务时,它的作用就显现出来了。必须说明的是,在上文中我特意注明了getInstance()方法是加了锁的,如果没有daoMap,则每次打开数据库都需要调用构造方法(实际上是createDatabase()方法),如此,效率是低下的;

  因此,使用DbUtils打开数据库是线程安全的

  然而,使用DbUtils关闭数据库是非线程安全的;正是在不正确的时机调用DbUtils的close方法造成了上文中两个IllegalStateException的抛出;因为用户在每次完成数据库操作后,都需要调用DbUtils的close方法,而在该方法内部则不加判断的调用管理SQLiteDatabase.close()方法,造成了抛出异常;而如果用户在打开数据库后不调用DbUtils的close方法,则会收到警告,database never closed;

  我认为,DbUtils的close方法的实现是粗糙的,它没有考虑到多线程的情况;在很长一段时间内,为了规避这个问题,我一直在主线程中进行数据库操作,然而并不是所有的数据库操作任务都是迅速的;这也是促使我解决这个问题的直接原因,并且超过1秒的卡顿是用户所不能接受的;

  解决这个问题的思路其实很简单:给每个数据库对象都记个数(count)----表示当前有count处使用了对象;在getInstance处使count加1,在close()中先使count减1,当count==0时才去执行真正的close方法(SQLiteDatabase.close());

    /**key = dbName value = linkCount;*/
    private static HashMap<String, Integer> countMap = new HashMap<String, Integer>();
    
    /** 增加;减少*/
    private static int TYPE_ADD = 0,TYPE_DEC=1;
    
    /**
     * 在getInstance的末尾和close方法的起始处调用
     */
    private static synchronized void manageCount(String dbName,int type){
        
        int count = countMap.get(dbName)==null?0:countMap.get(dbName);
        
        if(type==TYPE_ADD){
            
            count++;
        }else if(type==TYPE_DEC){
            
            count--;
        }
        
        countMap.put(dbName, count);
    }

  我要偷个懒,不上实验数据了,第一次写,不知道要这么长时间的。

0 0
原创粉丝点击