Android многопоточность и блокировка базы данных - PullRequest
28 голосов
/ 15 апреля 2010

Мы используем 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 ]

28 голосов
/ 23 апреля 2010

Мы использовали ContentProvider в конце. Это, казалось, прояснило проблемы.

16 голосов
/ 28 февраля 2012

Я решил то же самое исключение, просто убедившись, что все мои открытия базы данных закрыты, и (что более важно), чтобы убедиться в этом, сделав область действия каждого экземпляра базы данных локальной ТОЛЬКО для метода, который нуждается в этом. ContentProvider - это хороший, безопасный класс для использования при доступе к БД из нескольких потоков, но также убедитесь, что вы используете хорошие методы работы с БД:

  • Сохранять экземпляры БД локально (без членов класса SQLiteDatabase!)
  • вызов close() на БД в том же методе, в котором он открыт
  • звоните close() по курсорам, которые вы получаете из базы данных
  • прислушивается к LogCat для любых жалоб, которые может иметь SQLiteDatabse
11 голосов
/ 13 ноября 2013

Перед некоторым кодом, давайте возобновим некоторые подходы:

  • Семафоры : безусловно, лучшее из представленных решений. Это лежит в основе проблемы: разделение ресурсов! Он будет обрабатывать блокировку доступа к базе данных, избегая конфликтов (database is locked).

  • Синхронизация Java : своего рода реализация семафора, но менее изощренная. Используя synchronized, вы не легко решите некоторые случаи, связанные с транзакциями.

  • ContentProvider : внедрить ContentProvider решить проблему только в некоторых случаях (или скрыть проблему под ковром). Вы еще столкнетесь с теми же проблемами. Разница в том, что шаблон ContentProvider поможет вам не совершать некоторых ошибок при доступе к базе данных Sqlite. ContentProvider docs гласит: «Вам не нужен поставщик, чтобы использовать базу данных SQLite, если использование полностью в вашем собственном приложении».

  • Почти обязательно : сохранить экземпляры БД локальными, вызвать close() в БД в том же методе, в котором он открыт с использованием операторов finally, close() для курсоров, использующих операторы finally и т. д., почти является обязательным, чтобы избежать проблем с использованием Sqlite.

Давайте покажем пример решения семафора, представленного Моссом , который я взял из 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();
            }
        });
    }
}
9 голосов
/ 17 апреля 2010

Примите во внимание, что базы данных SQLite основаны на файлах и не предназначены для доступа к ним в многопроцессорном режиме. Лучшая процедура смешивания SQLite с мультипроцессором - это использование семафоров (aquire (), release ()) в каждом доступе к базе данных.

Если вы создаете оболочку Db, которая создает / выпускает глобальный семафор, ваш доступ к БД будет поточно-ориентированным. Действительно, это означает, что вы можете получить загрузочную шею, потому что вы ставите в очередь доступ к БД. Поэтому, кроме того, вы можете обернуть доступ с помощью семафоров только в том случае, если это операция, которая изменяет базу данных, поэтому, пока вы изменяете базу данных, никто не сможет получить к ней доступ и дождаться завершения процесса записи.

1 голос
/ 11 июля 2014

Мы не можем совместно использовать соединение 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;
}
0 голосов
/ 29 ноября 2011

Вы должны вызывать getWritableDatabase() из функции, а не из конструктора вспомогательного класса db. Если объект класса db helper создается с SQLiteDatabase.openOrCreateDatabase(DB_PATH, null); или подобным, а затем из функции вызывается getWritableDatabase(), он попытается сделать синхронный вызов БД, что вызовет исключение блокировки БД.

0 голосов
/ 16 апреля 2010

Вы говорите об одном пользовательском действии, которое внутри вашей программы вызывает запуск нескольких потоков, более одного из которых может обращаться к базе данных в режиме обновления?

Это плохой дизайн, точка. У вас нет возможности узнать, в каком порядке потоки будут запланированы вашей ОС (/ VM), и, следовательно, у вас нет возможности узнать, в каком порядке будут происходить обращения к базе данных, и это весьма вероятно подразумевает что у вас нет возможности узнать, что доступ к базе данных всегда будет происходить в том порядке, в котором вы ожидаете.

Все обращения к базе данных, генерируемые / поступающие от какого-либо действия пользователя, должны выполняться в одном потоке.

...