Android线程和数据库锁定
我们正在使用Android线程和数据库锁定,android,sqlite,locking,thread-safety,android-asynctask,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)
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)
E/SQLiteOpenHelper(15963):无法打开iviewnews.db进行写入(将尝试只读):
E/SQLiteOpenHelper(15963):android.database.sqlite.SQLiteException:数据库已锁定
E/SQLiteOpenHelper(15963):位于android.database.sqlite.SQLiteDatabase.native_setLocale(native方法)
E/SQLiteOpenHelper(15963):位于android.database.sqlite.SQLiteDatabase.setLocale(SQLiteDatabase.java:1637)
E/SQLiteOpenHelper(15963):位于android.database.sqlite.SQLiteDatabase.(SQLiteDatabase.java:1587)
E/SQLiteOpenHelper(15963):位于android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:638)
E/SQLiteOpenHelper(15963):位于android.database.sqlite.SQLiteDatabase.openOrCreateDatabase(SQLiteDatabase.java:659)
E/SQLiteOpenHelper(15963):位于android.database.sqlite.SQLiteDatabase.openOrCreateDatabase(SQLiteDatabase.java:652)
E/SQLiteOpenHelper(15963):位于android.app.ApplicationContext.openOrCreateDatabase(ApplicationContext.java:482)
E/SQLiteOpenHelper(15963):位于android.content.ContextWrapper.openOrCreateDatabase(ContextWrapper.java:193)
E/SQLiteOpenHelper(15963):位于android.database.sqlite.SQLiteOpenHelper.getwriteabledatabase(SQLiteOpenHelper.java:98)
E/SQLiteOpenHelper(15963):位于android.database.sqlite.SQLiteOpenHelper.getReadableDatabase(SQLiteOpenHelper.java:158)
E/SQLiteOpenHelper(15963):位于com.iview.android.widget.IViewNewsTopStoryWidget.initData(IViewNewsTopStoryWidget.java:73)
E/SQLiteOpenHelper(15963):位于com.iview.android.widget.IViewNewsTopStoryWidget.updateNewsWidgets(IViewNewsTopStoryWidget.java:121)
E/SQLiteOpenHelper(15963):位于com.iview.android.async.GetNewsTask.doInBackground(GetNewsTask.java:338)
E/SQLiteOpenHelper(15963):位于com.iview.android.async.GetNewsTask.doInBackground(GetNewsTask.java:1)
E/SQLiteOpenHelper(15963):位于android.os.AsyncTask$2.call(AsyncTask.java:185)
E/SQLiteOpenHelper(15963):位于java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:256)
E/SQLiteOpenHelper(15963):位于java.util.concurrent.FutureTask.run(FutureTask.java:122)
E/SQLiteOpenHelper(15963):位于java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:648)
E/SQLiteOpenHelper(15963):位于java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:673)
E/SQLiteOpenHelper(15963):位于java.lang.Thread.run(Thread.java:1060)
对于从不同线程写入数据库而不是从读取线程写入数据库的代码,是否有人有一个通用示例?我们如何确保线程安全
我的一个建议是使用
ContentProvider
,因为这将处理来自多个线程的数据库访问。我会看看这个,但这是处理此类问题的推荐方法吗?考虑到我们在前面或后面谈论的内容,它似乎相当重要。您是否在谈论一个用户操作,该操作在程序内部导致多个线程运行,其中多个线程可能在更新模式下访问数据库
这是个糟糕的设计,句号。您无法知道OS(/VM)将以何种顺序调度线程,因此您无法知道数据库访问将以何种顺序进行,这很可能意味着您无法知道数据库访问将始终以您期望的顺序进行
由某个用户操作生成/来自某个用户操作的所有数据库访问都应在一个线程中完成。请考虑到SQLite数据库是基于文件的,不希望以多进程方式访问。将SQLite与多处理混合的最佳过程是在每个与数据库相关的访问中使用信号量(aquire(),release())
如果您创建一个Db包装器来获取/释放一个全局信号量,那么您的Db访问将是线程安全的。实际上,这意味着您可以获得bootleneck,因为您正在排队访问数据库。因此,除此之外,如果是更改数据库的操作,则只能使用信号量包装访问,因此,当您更改数据库时,没有人能够访问它并等待写入过程完成。我们最终使用了
ContentProvider
。这似乎解决了问题。您必须从函数而不是db helper类的构造函数调用getWritableDatabase()
。如果使用SQLiteDatabase.openOrCreateDatabase(db_PATH,null)创建db helper类对象代码>或类似,然后从函数调用getWritableDatabase()
,它将尝试对DB进行同步调用,从而导致DB锁异常 我只是通过确保所有打开的数据库都有关闭来解决这个异常,而且(更重要的是)为了确保这一点,使每个数据库实例的作用域仅限于需要它的方法。ContentProvider是从多个线程访问db时使用的一个良好、安全的类,但也要确保您使用的是良好的db实践:
- 使数据库实例保持本地(没有SQLiteDatabase类成员!)
- 在数据库上调用
close()
,方法与打开数据库的方法相同
- 对从db获得的游标调用
close()
- 听取LogCat对SQLiteDatabase可能存在的任何投诉
在编写代码之前,让我们继续一些方法:
- :目前为止提出的最佳解决方案。这是问题的核心:资源共享!它将处理数据库访问的锁定,避免冲突(
数据库已锁定
)
- :一种信号量实现,但不太具体。使用
synchronized
无法轻松解决某些涉及tr的案例
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();
}
});
}
}
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();
}
}
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;
}