注意:请勿将此问题标记为重复。我已经经历了几个类似的问题,但无法找到满意的答案。
我一直在研究使用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
类并对数据库对象执行操作。在阅读其中一篇帖子时,post提到在使用Content Provider时,您无需手动处理多线程环境。
虽然ContentProvider缺乏线程安全性,但您经常会发现在防止潜在的竞争条件方面您无需采取进一步行动。规范示例是您的ContentProvider由SQLiteDatabase支持的时间;当两个线程同时尝试写入数据库时,SQLiteDatabase将自行锁定,确保一个等待直到另一个完成。每个线程都将获得对数据源的互斥访问,确保满足线程安全。
以上引用似乎令人困惑,因为首先它提到ContentProvider不支持线程安全。但他得出的结论是,应用程序开发人员并不需要做任何事情来实现并发。
另外,如果我选择使用SqliteOpenHelper,那么防止这些崩溃的最佳方法是什么?我一直在考虑为每个数据库操作使用锁。
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上最受欢迎的projects之后,我发现开发人员建议将每个数据库操作包装在一个事务中。
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类的一个实例。但仍然遇到这个错误。
P.S:感谢您提出这么长的问题。
答案 0 :(得分:3)
使用Singleton
模式来实例化SQLiteOpenHelper
,因此在整个应用程序中应该存在一个单例实例。 这将确保不会发生泄漏,并且会使您的生活变得更加轻松,因为它可以避免在编码时忘记关闭数据库。它还将确保在整个应用程序中安全访问数据库。
此外,您不必实施自己的锁定机制。 SQLiteDatabase
保持锁定机制。因此,在特定时间只会有一笔交易,所有其他交易将使用Queue
方法在Sigleton.getInstance()
中。确保数据库的单个访问点。
此外,在此方法中, 您不必关闭数据库连接,因为SQLiteDatabase将在事务完成后释放所有引用。 所以{{每当您想要执行CRUD操作并删除锁定mecansim时,都应该使用1}}。
更多信息,请浏览博客http://www.androiddesignpatterns.com/2012/05/correctly-managing-your-sqlite-database.html并查看评论。
希望这有帮助。
答案 1 :(得分:1)
查询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;
}
}
请查看此article以了解双重锁定的问题。
看看相关的SE问题:
What is an efficient way to implement a singleton pattern in Java?
我更喜欢创建Singleton
private static final CustomSqliteHelper instance = new CustomSqliteHelper ();
其他方法:
enum CustomSqliteHelper {
INSTANCE;
}
如果你仍然需要懒惰的单身人士,还有一种方法。
在此SE问题中查看@ Nathan Hughes
答案:
Synchronized and locks in singleton factory
查询2 :(锁定)
使用java锁定提高应用程序的一致性并不是一个坏主意。一致性胜过费用。
从SQLiteDatabaseLockedException页面
如果数据库引擎无法获取数据库锁,则需要执行其工作。如果语句是[COMMIT]或发生在显式事务之外,则可以重试该语句。如果该语句不是[COMMIT]并且发生在显式事务中,那么您应该在继续之前回滚该事务。
我希望您应该遵循上述建议以及您在问题(Github)项目中发布的代码段。我喜欢这个想法,上面的建议与Github示例代码一致。
答案 2 :(得分:1)
我肯定会建议您将数据库包装在ContentProvider
。
它提供了开箱即用的好功能,并将许多潜在的棘手任务转移到框架中。您不需要处理单例数据库帮助程序,因为您可以保证您的应用程序有一个ContentResolver
实例,可以将命令转发给目标提供程序。
您可以使用整个Loader
框架和好的帮助程序类(如AsyncQueryHandler
)来处理一些最常见的数据库访问警告。
我建议不要同步对CRUD方法的访问。正如文档和之前的答案中所提到的,这应该由数据库本身来处理。更不用说在需要大量数据库使用的应用程序中潜在的性能损失。
旁注:作为第一步,如果您尝试找出导致这些崩溃的原因,这可能是有用的 - 它们是零星的吗?在进行故障排除时,更具体的示例可能非常有用。
答案 3 :(得分:0)
您可以使用内容提供商。
答案 4 :(得分:0)
我认为在大多数情况下使用读写锁就足够了。见this answer。该解决方案不使用ContentProvider
。相反,它使用ReentrantReadWriteLock
包中的java.util.concurrent.locks
类。
答案 5 :(得分:0)
- 鉴于我的应用程序不与其他应用程序共享数据,使用ContentProviders或扩展SqliteOpenHelper是更好的选择。
醇>
我说如果您不共享任何数据,扩展SqliteOpenHelper
是更好的选择。这主要是因为它比ContentProvider
更加开发友好的API :-)还有official documentation中描述的其他内容:
决定是否需要内容提供商。如果要提供以下一项或多项功能,则需要构建内容提供程序:
- 您希望向其他应用程序提供复杂的数据或文件。
- 您希望允许用户将应用中的复杂数据复制到其他应用中。
- 您希望使用搜索框架提供自定义搜索建议。
所以在你的情况下 - 它是多余的
对于单身初始化 - 有时候你不需要它进行懒惰的初始化:
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
的文档答案 6 :(得分:0)
除了确保SQLiteOpenHelper是单例之外,如果您希望多个线程访问数据库,还需要启用预写日志记录:
这样做可以解决我的问题。
答案 7 :(得分:-2)
正如评论中所讨论的,如果您使用一个数据库实例,则不会有阻止锁定。 所以我怀疑DeadLock是因为你正在使用交易。
一种DeadLock示例是:
在一次交易中:
更新表格 - >更新B表 - >提交
在其他交易中:
更新B表 - >更新表格 - >提交
所以在中间时间,1等待2和2同时等待1,所以死锁发生。
如果它真的是死锁,你可以修复查询,以免发生这种情况 或者您可以使用像Actor这样的排队查询(或异步查询)。
例如,commons-dbutils库支持AsyncQueryRunner,它提供ExecutorService并返回Future。 (来自:Is asynchronous jdbc call possible?)答案 8 :(得分:-2)
要防止锁定错误,请在每个连接中设置大约20秒左右的busy timeout。然后,只有当您的应用程序挂起运行的事务的时间超过该事务时,您才会收到异常。
您还可以使用所有线程中的单个SQLiteDatabase
对象,但是您必须在所有事务中手动锁定它。