[英]Best way to access database in multi threaded application Android?
注意:请不要将此问题标记为重复。 我已经经历了几个类似的问题,但找不到满意的答案。
我一直在研究使用Sqlite数据库的应用程序。 我们遵循单例模式,确保我们只能在整个应用程序中创建一个辅助类实例。
public class CustomSqliteHelper extends SQLiteOpenHelper {
public static CustomSqliteHelper getInstance(Context context) {
if (instance == null) {
synchronized (CustomSqliteHelper.class) {
if (instance == null) {
instance = new CustomSqliteHelper(context);
}
}
}
return instance;
}
}
但有时应用程序崩溃与SQLiteDatabaseLockedException
。 我理解当多个线程/进程尝试一次写入数据库时会出现此异常。 即使一个线程/进程在写入操作仍在进行时尝试读取数据库,也会抛出此异常。
所以我一直在阅读很多关于这个以及防止这种情况发生的可能方法。 很多帖子建议使用ContentProvider而不是直接扩展SqliteOpenHelper
类并对数据库对象执行操作。 在阅读其中一篇帖子时,这篇文章提到在使用Content Provider时,您不需要手动处理多线程环境。
尽管ContentProvider缺乏线程安全性,但通常您会发现在防止潜在的竞争条件方面您无需采取进一步行动。 规范示例是您的ContentProvider由SQLiteDatabase支持的时间; 当两个线程同时尝试写入数据库时,SQLiteDatabase将自行锁定,确保一个等待直到另一个完成。 每个线程都将获得对数据源的互斥访问,从而确保满足线程安全性。
以上引用似乎令人困惑,因为首先它提到ContentProvider不支持线程安全。 但他总结说,应用程序开发人员不需要做任何事情来实现并发。
另外,如果我选择使用SqliteOpenHelper,那么防止这些崩溃的最佳方法是什么? 我一直在考虑为每个db操作使用锁。
public class CustomSqliteHelper extends SQLiteOpenHelper {
private String lock = "lock";
public void insert(){
synchronized(lock){
// Do the insert operation here.
}
}
public void update(){
synchronized(lock){
// Do the update operation here.
}
}
}
但我的一个团队成员建议我不要这样做,因为Java锁很贵。
在浏览了Github上最受欢迎的项目之一后,我发现开发人员建议将每个数据库操作包装在一个事务中。
public void insert(ContentValues values) {
// Create and/or open the database for writing
SQLiteDatabase db = getWritableDatabase();
// It's a good idea to wrap our insert in a transaction. This helps with performance and ensures
// consistency of the database.
db.beginTransaction();
try {
// The user might already exist in the database (i.e. the same user created multiple posts).
db.insertOrThrow(TABLE_POSTS, null, values);
db.setTransactionSuccessful();
} catch (Exception e) {
Log.d(TAG, "Error while trying to add post to database");
} finally {
db.endTransaction();
}
}
我真的不确定这是否可以阻止Lock异常? 这似乎更像是一个以绩效为导向的步骤。
所以在阅读完所有博客和教程之后,我仍然感到困惑。 我的主要问题是
更新:根据@Mustanar的回答,似乎SQLiteDatabase负责锁定机制。 这意味着如果您正在执行写操作,则数据库将被锁定。 但同时,如果某个其他线程尝试执行写操作,那么第二个操作是否会等待直到锁被释放或者是否会抛出android.sqlite.database.SQLiteDatabaseLockedException
异常?
更新2 :给这个问题一个赏金,因为答案似乎仍然不清楚。 我只使用Helper类的一个实例。 但仍然得到这个错误。
PS:感谢您提出这么长的问题。
使用Singleton
模式来实例化SQLiteOpenHelper
,因此在整个应用程序中应该存在一个单例实例。 这将确保不会发生泄漏,并且可以使您的生活更轻松,因为它消除了在编码时忘记关闭数据库的可能性。 它还将确保在整个应用程序中安全访问数据库。
而且,您不必实现自己的锁定机制。 SQLiteDatabase
维护锁定机制。 因此,在特定时间只有一个事务发生,所有其他事务将使用Sigleton.getInstance()
方法在Queue
。 确保数据库的单个访问点。
此外,在此方法中, 您不必关闭数据库连接,因为SQLiteDatabase将在事务完成后释放所有引用。 因此,只要您想执行CRUD操作并删除锁定mecansim,就应该使用CustomSQLiteHelper.getInstance()
。
更多信息,请访问博客http://www.androiddesignpatterns.com/2012/05/correctly-managing-your-sqlite-database.html并查看评论。
希望这可以帮助。
查询1(单例):
你能改变下面的双锁定代码来创建一个真正的Singleton对象吗?
public class CustomSqliteHelper extends SQLiteOpenHelper {
private CustomSqliteHelper instance;
public static CustomSqliteHelper getInstance(Context context) {
if (instance == null) {
synchronized (CustomSqliteHelper.class) {
if (instance == null) {
instance = new CustomSqliteHelper(context);
}
}
}
return instance;
}
}
看看这篇文章 ,了解双重锁定的问题。
看看相关的SE问题:
我更喜欢创建Singleton
private static final CustomSqliteHelper instance = new CustomSqliteHelper ();
其他方法:
enum CustomSqliteHelper {
INSTANCE;
}
如果你还需要懒惰的Singleton,还有一种方法。
在这个SE问题中看看@ Nathan Hughes
回答:
查询2 :(锁定)
使用java锁定可以提高应用程序的一致性。 一致性胜过费用。
从SQLiteDatabaseLockedException页面,
如果数据库引擎无法获取数据库锁,则需要执行其工作。 如果语句是[COMMIT]或发生在显式事务之外,则可以重试该语句。 如果该语句不是[COMMIT]并且在显式事务中发生,那么您应该在继续之前回滚事务。
我希望您应该遵循上面的建议以及您在问题(Github)项目中发布的代码片段。 我喜欢这个想法,上面的建议与Github示例代码一致。
我肯定会建议您将数据库包装在ContentProvider
。
它提供了开箱即用的好功能,并将许多潜在的棘手任务转移到框架中。 您不需要处理单例数据库帮助程序,因为您可以保证您的应用程序有一个ContentResolver
实例,可以将命令转发给目标提供程序。
您可以使用整个Loader
框架和漂亮的帮助程序类(如AsyncQueryHandler
来处理一些最常见的数据库访问警告。
我建议不要同步对CRUD方法的访问。 正如文档和之前的答案中所提到的,这应该由数据库本身来处理。 更不用说在需要密集数据库使用的应用程序中潜在的性能损失。
旁注:作为第一步,如果您尝试找出导致这些崩溃的原因可能会有用 - 它们是否零星? 一个更具体的例子可能在故障排除时有用。
您可以使用内容提供商。
我认为在大多数情况下使用读写锁就足够了。 看到这个答案 。 该解决方案不使用ContentProvider
。 相反,它在java.util.concurrent.locks
包中使用ReentrantReadWriteLock
类。
- 鉴于我的应用程序不与其他应用程序共享数据,使用ContentProviders或扩展SqliteOpenHelper是更好的选择。
我想如果你不共享任何数据,扩展SqliteOpenHelper
是更好的选择。 这主要是因为它比ContentProvider
更加开发友好的API :-)还有官方文档中描述的其他内容 :
确定您是否需要内容提供商。 如果要提供以下一项或多项功能,则需要构建内容提供程序:
- 您希望向其他应用程序提供复杂的数据或文件。
- 您希望允许用户将应用中的复杂数据复制到其他应用中。
- 您希望使用搜索框架提供自定义搜索建议。
所以在你的情况下 - 它是多余的
对于单例初始化 - 有时你不需要它进行惰性初始化:
public class CustomSqliteHelper {
private static final CustomSqliteHelper instance = new CustomSqliteHelper(YourApplication.getContext());
public static CustomSqliteHelper getInstance() {
return instance;
}
// all the super staff below :)
}
但是您需要在应用程序类中公开应用程序上下文:
public class YourApplication extends Application {
private static final YourApplication instance;
@Override
public void onCreate() {
super.onCreate();
instance = this;
}
public static Context getContext() {
return instance.getApplicationContext();
}
}
加上Manifest中的配置。 这样可以避免在创建过程中同步。
如果你真的需要这个单例进行延迟初始化 - 比我说双重检查锁定 (DCL)是非常糟糕的做法。 对于安全的延迟初始化,您应该使用以下模式:
public class CustomSqliteHelper {
private static class InstanceHolder {
public static CustomSqliteHelper instance = new CustomSqliteHelper(YourApplication.getContext());
}
public CustomSqliteHelper getInstance() {
return InstanceHolder.instance;
}
// all the super staff below :)
}
- 将Java锁定放在所有操作上是最好的方法还是有比这更好的其他方法? 这提供安全的延迟初始化而不锁定任何东西。
没有必要进行任何锁定(直到您的DAO无状态)。 检查SQLiteDatabase的文档
除了确保SQLiteOpenHelper是单例之外,如果希望多个线程访问数据库,还需要启用预写日志记录:
这样做可以解决我的问题。
正如在注释中所讨论的,如果您使用一个数据库实例,则不会出现阻塞锁定。 所以我怀疑DeadLock是因为你正在使用交易。
一种DeadLock示例是:
在一次交易中:
更新表 - >更新B表 - >提交
在其他交易中:
更新B表 - >更新表 - >提交
所以在中间时间,1等待2和2同时等待1,所以死锁发生。
如果它确实是死锁,您可以修复查询以不发生这种情况,或者您可以使用像Actor这样的排队查询(或异步查询)。
例如,commons-dbutils库支持AsyncQueryRunner,它提供ExecutorService并返回Future。 (来自: 是否可以进行异步jdbc调用? )
要防止锁定错误,请在每个连接中将忙碌超时设置为20秒左右。 然后,只有当您的应用程序挂起运行的事务超过该事务时,才会获得异常。
您还可以使用来自所有线程的单个SQLiteDatabase
对象,但是您必须在所有事务周围手动锁定它。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.