Android线程和数据库锁定

时间:2010-04-15 17:23:50

标签: android sqlite locking thread-safety android-asynctask

我们正在使用AsyncTasks来访问数据库表和游标。

不幸的是,我们偶尔会看到有关数据库被锁定的例外情况。

E/SQLiteOpenHelper(15963): Couldn't open iviewnews.db for writing (will try read-only):
E/SQLiteOpenHelper(15963): android.database.sqlite.SQLiteException: database is locked
E/SQLiteOpenHelper(15963):  at     android.database.sqlite.SQLiteDatabase.native_setLocale(Native Method)
E/SQLiteOpenHelper(15963):  at     android.database.sqlite.SQLiteDatabase.setLocale(SQLiteDatabase.java:1637)
E/SQLiteOpenHelper(15963):  at     android.database.sqlite.SQLiteDatabase.<init>(SQLiteDatabase.java:1587)
E/SQLiteOpenHelper(15963):  at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:638)
E/SQLiteOpenHelper(15963):  at android.database.sqlite.SQLiteDatabase.openOrCreateDatabase(SQLiteDatabase.java:659)
E/SQLiteOpenHelper(15963):  at android.database.sqlite.SQLiteDatabase.openOrCreateDatabase(SQLiteDatabase.java:652)
E/SQLiteOpenHelper(15963):  at android.app.ApplicationContext.openOrCreateDatabase(ApplicationContext.java:482)
E/SQLiteOpenHelper(15963):  at android.content.ContextWrapper.openOrCreateDatabase(ContextWrapper.java:193)
E/SQLiteOpenHelper(15963):  at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:98)
E/SQLiteOpenHelper(15963):  at android.database.sqlite.SQLiteOpenHelper.getReadableDatabase(SQLiteOpenHelper.java:158)
E/SQLiteOpenHelper(15963):  at com.iview.android.widget.IViewNewsTopStoryWidget.initData(IViewNewsTopStoryWidget.java:73)
E/SQLiteOpenHelper(15963):  at com.iview.android.widget.IViewNewsTopStoryWidget.updateNewsWidgets(IViewNewsTopStoryWidget.java:121)
E/SQLiteOpenHelper(15963):  at com.iview.android.async.GetNewsTask.doInBackground(GetNewsTask.java:338)
E/SQLiteOpenHelper(15963):  at com.iview.android.async.GetNewsTask.doInBackground(GetNewsTask.java:1)
E/SQLiteOpenHelper(15963):  at android.os.AsyncTask$2.call(AsyncTask.java:185)
E/SQLiteOpenHelper(15963):  at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:256)
E/SQLiteOpenHelper(15963):  at java.util.concurrent.FutureTask.run(FutureTask.java:122)
E/SQLiteOpenHelper(15963):  at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:648)
E/SQLiteOpenHelper(15963):  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:673)
E/SQLiteOpenHelper(15963):  at java.lang.Thread.run(Thread.java:1060)

是否有人有一个代码的一般示例,该代码从不同于一个读取的线程写入数据库,以及如何确保线程安全。

我有一个建议是使用ContentProvider,因为这将处理来自多个线程的数据库访问。我将看看这个,但这是处理这样一个问题的推荐方法吗?考虑到我们在前面或后面谈论它,它似乎相当重量级。

7 个答案:

答案 0 :(得分:28)

我们最后使用了ContentProvider。这似乎可以解决问题。

答案 1 :(得分:16)

我只是通过确保所有数据库打开都已关闭来解决同样的异常,并且(更重要的是)确保这一点,使每个数据库实例的范围仅限于需要它的方法。 ContentProvider是从多个线程访问数据库时使用的一个好的,安全的类,但也确保您使用了良好的数据库实践:

  • 保持db实例本地(没有SQLiteDatabase类成员!)
  • 使用与其打开相同的方法在数据库上调用close()
  • 在您从db
  • 获取的游标上调用close()
  • 听取LogCat对SQLiteDatabse可能有的任何抱怨

答案 2 :(得分:11)

在某些代码之前,让我们恢复一些方法:

  • Semaphores :到目前为止提供的最佳解决方案。它是问题的核心:资源共享!它将处理数据库访问的锁定,避免冲突(database is locked)。

  • Java synchronization :一种信号量实现,但不那么软化了。使用synchronized您将无法轻松解决涉及交易的一些案例。

  • ContentProvider :实施ContentProvider仅针对某些情况解决问题(或扫除地毯下的问题)。你还会遇到同样的问题。区别在于ContentProvider模式将指导您在访问Sqlite数据库时不会出现一些常见错误。 ContentProvider docs说:“如果使用完全在您自己的应用程序中,则不需要提供程序来使用SQLite数据库。”

  • Almost mandatory :将数据库实例保持为本地状态,使用close()语句以与finally语句打开相同的方法在数据库上调用close() {使用finally语句等游标上的{1}} 几乎必须使用Sqlite来避免问题。

让我们展示一个提供by Moss的信号量解决方案的示例,我从CL.获取并改编为涵盖交易。

class DataAccess {
    private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    private final Lock r = rwl.readLock();
    private final Lock w = rwl.writeLock();

    public Data readSomething(int id) {
        Cursor c = null;
        r.lock();
        try {
            c = getReadableDatabase().query(...);
            return c.getString(0);
        } finally {
            if (c != null) c.close();
            r.unlock();
        }
    }

    public void changeSomething(int id, int value) {
        w.lock();
        try {
            getWritableDatabase().update(...);
        } finally {
            w.unlock();
        }
    }

    private void beginTransactionWithSemaphores() {
        getWritableDatabase().beginTransactionWithListener(new SQLiteTransactionListener() {
            @Override
            public void onBegin() {
                w.lock();
            }

            @Override
            public void onRollback() {
                w.unlock();
            }

            @Override
            public void onCommit() {
                w.unlock();
            }
        });
    }
}

答案 3 :(得分:9)

考虑到SQLite数据库是基于文件的,并不能以多进程方式访问。将SQLite与多处理混合的最佳步骤是在每个与数据库相关的访问中使用信号量(aquire(),release())。

如果您创建一个获取/释放全局信号量的Db包装器,您的数据库访问将是线程安全的。实际上这意味着您可以获得一个bootleneck,因为您正在排队访问数据库。所以另外你只能使用信号量来包装访问,如果它是一个改变数据库的操作,那么当你改变数据库时,没有人能够访问它并等到写入过程完成。

答案 4 :(得分:1)

我们无法与多个线程共享Db连接以在数据库中同时执行读写操作。我们将使用同步概念创建数据库的单个对象,我们将一次执行一个任务。我们将使用单例模式制作数据库对象,它将在多个线程中共享。一次将执行单个任务。然后我们将在DB上启动其他任务或任何操作。 内容提供程序不是数据库锁定问题的解决方案。

import java.util.concurrent.atomic.AtomicInteger;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;

public class DatabaseManager {

private AtomicInteger mOpenCounter = new AtomicInteger();

private static DatabaseManager instance;
private static SQLiteOpenHelper mDatabaseHelper;
private SQLiteDatabase mDatabase;
//private static String DB_PATH = "";
//  private static String DB_NAME = "xyz.db";// Database name
private static String dbPathh;

public static synchronized void initializeInstance(SQLiteOpenHelper helper,
        String dbPath) {
    if (instance == null) {
        instance = new DatabaseManager();
        mDatabaseHelper = helper;
        dbPathh=dbPath;
    }
  }

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(String thread) {

    if(mOpenCounter.get() == 0) {
        // Opening new database
        // mDatabase = mDatabaseHelper.getWritableDatabase();
        MyLog.e("Path Of DataBase", dbPathh);
        //  mDatabase=mDatabaseHelper.getWritableDatabase();
        mOpenCounter.incrementAndGet();
        mDatabase=SQLiteDatabase.openDatabase(dbPathh, null,   
 SQLiteDatabase.  CREATE_IF_NECESSARY|SQLiteDatabase.OPEN_READWRITE);   
        MyLog.e("Open Data Base", " New Connection created" +thread);
    }
    else{
        MyLog.e("Open Data Base", " Old Connection given " +thread);
    }
    //  Toast.makeText(NNacres.getConfig(), "open conn: present connection = 
   "   +mOpenCounter.get(), Toast.LENGTH_LONG).show();
    return mDatabase;
   }

    public synchronized void closeDatabase() {
    MyLog.e("Close db connection", ""+mOpenCounter.get());

    if(mOpenCounter.get() == 1) {
        // Closing database

        mDatabase.close();
        mOpenCounter.decrementAndGet();

        Log.e("DB CLOSED", "DONE");
    }
    //Toast.makeText(NNacres.getConfig(), "close conn: after close =   
 " +mOpenCounter.get(), Toast.LENGTH_LONG).show();
    }

    }

并在YourSQLiteDataABse帮助器类中编写此方法,该类扩展了SQLiteOpenHelper类

     public SQLiteDatabase getWritableDatabase() {
DatabaseManager.initializeInstance(this,"data/data/your packgae name/databases/xyz");
    return DatabaseManager.getInstance().openDatabase(getClass().getSimpleName());

}



public static String getMyDbPath(String DB_NAME, Context context) {

    String myDbPath = context.getDatabasePath(DB_NAME).getPath();
    MyLog.e("DB Path: "+myDbPath);
    return myDbPath;
}

答案 5 :(得分:-3)

您必须从函数调用getWritableDatabase(),而不是db helper类的构造函数。如果使用SQLiteDatabase.openOrCreateDatabase(DB_PATH, null);或类似函数创建db帮助程序类对象,然后从函数调用getWritableDatabase(),它将尝试对DB进行同步调用,从而导致数据库锁定异常。

答案 6 :(得分:-6)

您是在谈论在您的程序中导致多个线程运行的单个用户操作,其中多个线程可能在更新模式下访问数据库?

这是糟糕的设计,期间。您无法知道您的操作系统(/ VM)将以何种顺序调度线程,因此您无法知道数据库访问将以何种顺序发生,这很可能意味着您无法知道数据库访问将始终按您期望的顺序进行。

由/来自某些用户操作生成的所有数据库访问都应该在一个单独的线程中完成。