【问题标题】:Android SQLite Concurrency (trying to reopen an already closed connection)Android SQLite 并发(试图重新打开已经关闭的连接)
【发布时间】:2014-04-06 21:58:53
【问题描述】:

这类问题被问了很多,但我找不到任何让我满意的答案。

问题
我应该如何在多个线程上维护多个打开的 SQLite 连接?
是否可以检查打开了多少连接并仅在没有连接时才调用 close() ?

编辑
我接受了下面的答案,因为 SQLite 非常快,并且同步所有 sql API 方法(一次只运行一个)可能不会太伤人。
话虽如此,我仍然很高兴听到同步 read()s 和 write()s 的解决方案,以便可以一次完成多个 read()...

背景
我正在构建一个 Android 应用程序,在某些地方我使用的 Internet 连接很少(GET 请求)。
当这些请求完成时,它们的所有线程都访问 SQLite,大部分用于编写。
当线程尝试同时连接时,我收到标题中提到的错误。

我知道为什么会这样:
我这样做的方式是,每个“SQLite API”方法(我编写的 API)都是从打开连接开始并通过关闭它来完成的。需要时使用写入/读取 open()。

因此,当一个线程关闭连接(API 方法完成)而另一个仍在使用游标或其他线程时,会发生错误并引发此异常。

下面是一个代码示例来演示:

public class MySQL{
    private SQLiteOpenHelper mHelper;
    private SQLiteDatabase myDatabase;
    private static MySQL myInstance;
    ...

    //singleton
    public synchronized static MySQL getInstance(Context context){
        if(myInstance == null){
            myInstance = new MySQL(context);
        }
        synchronized (myInstance) {
            return myInstance;
        }
    }

    public SQLiteDatabase openRead() throws SQLException{
        return myDatabase = mHelper.getReadableDatabase();
    }


    public SQLiteDatabase openWrite() throws SQLException{ 
        return myDatabase = mHelper.getWritableDatabase();
    }

    //close helper
    public void close(){
        mHelper.close();
    }

    public static void createSomeEntry(){
         openWrite();
         myDatabase.query(...);
         ...
         close();
    }

    public static void getSomeEntry(){
         openRead();
         Cursor c = myDatabase.query(...);
         ...//use cursor in a loop
         close();
    }
}

因此,假设这些方法的任何两个组合同时从不同的线程运行。

如您所见,我尝试同步“myInstance”的使用/持有,希望只要一个线程持有这个变量,它就是唯一可以使用它的线程。
它显然不起作用,而且它只会减慢一切,我更喜欢更好的东西。

我该如何解决这个逻辑?

【问题讨论】:

    标签: java android multithreading sqlite


    【解决方案1】:

    SQLite 的 Java 驱动程序不支持真正的并发多线程访问。它只是“线程安全”的,因为从多个线程中使用不应该破坏数据。我认为你能做的最好的事情是打开一个连接,然后让每个线程在使用它时锁定连接,完成后放弃锁定。我不熟悉 Android 编程,但可能会使用以下方法:

    public class MySQL{
        private static MySQL myInstance;
        private SQLiteOpenHelper mHelper;
        private SQLiteDatabase myDatabase;
        ...
    
        //initialize
        public synchronized static void initialize(Context context){
            if(myInstance == null){
                myInstance = new MySQL(context);
            }
        }
    
        public synchronized static void createSomeEntry() {
            myInstance.myDatabase = myInstance.mHelper.getWritableDatabase();
            myInstance.myDatabase.query(...);
            ...
            myInstance.mHelper.close();
        }
    
        public synchronized static void getSomeEntry() {
            myInstance.myDatabase = myInstance.mHelper.getReadableDatabase();
            Cursor c = myInstance.myDatabase.query(...);
            ...//use cursor in a loop
            myInstance.mHelper.close();
        }
    }
    

    请注意将 getInstance() 替换为不返回任何内容的初始化函数,并消除除 createSomeEntry() 和 getSomeEntry() 之外的所有其他公共函数。由于类上的所有公共方法都是同步的,因此一次只能有一个线程使用数据库,这应该可以解决 SQLite 不支持并发访问的问题。

    如果您确实需要并发访问,我认为您的选择是弄清楚如何从多个进程而不是多个线程访问 SQLite 数据库,这正是我需要的方法,或者切换到另一个数据库。我想您也可以自己更改驱动程序以使驱动程序真正多线程。

    【讨论】:

    • 哦...我担心这就是答案。我觉得很奇怪,没有适用于 Android 的“并发 API”……你能给我看一个锁定连接的例子吗?我在 getInstance() 方法中的解决方案有什么问题?谢谢!
    • 我对 Android 特定的库不太熟悉,但您可以先将类中的所有内容设为私有,但 createSomeEntry() 和 getSomeEntry() 方法除外,这将是只有您的其他代码会使用的方法。使 createSomeEntry() 和 getSomeEntry() 方法同步将处理客户端线程的争用。您可能需要在课堂上进行一些额外的调整,但这只是一个开始。
    • 已经是这样了……使用这些函数的唯一方法就是调用 getInstance()。问题是,即使在同步这些方法时,也不意味着两个单独的方法不能同时访问数据库。必须有一个解决方案,使用我的 getInstance() 方法,同步实例...
    • 我是说将 getInstance() 方法也设为私有。客户端代码不会直接调用 getInstance()。你只能在你的 MySQL 类中调用它。
    • 有趣...我将如何更改我的代码以便它实际同步实例?我的意思是,仅此一项并不能解决问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-06
    相关资源
    最近更新 更多