【发布时间】:2014-02-24 10:22:02
【问题描述】:
我有一个班级需要成为单身人士:
private static StationsFile instance;
private Context ctx;
protected StationsFile(Context ctx){
this.ctx = ctx;
load();
}
public static synchronized StationsFile getInstance(Context ctx){
if(instance == null){
Log.d("StationsFile", "set instance " + StationsFile.class.hashCode());
instance = new StationsFile(ctx);
Log.d("StationsFile", "instance set " + instance.hashCode());
}
return instance;
}
protected static StationsFile getInstance(){
return getInstance(null);
}
private void load(){
if(externalStorageIsReadable()){
// Loads from sd file
}else{
loadDefaults();
}
}
private void loadDefaults(){
if(this.ctx != null){
// Load from raw file in raws folder (I need a context to access Resources)
}else{
// Hardcoded default here
}
}
现在,由于此方法是同步的并且是静态的,因此应该一次只调用一次。当我在我的设备(Android 4.4)中运行它时没有问题(在我的日志中我得到“设置实例 - 实例集”),但是当我在 Android 中第一次(并且仅在安装后第一次)运行它时2.2 虚拟设备或每次在 Android 4.4 虚拟设备中,我的日志中都会出现“set instance - set instance - instance set - instance set”,并因此导致崩溃。
所以看起来当启动“慢”时它会崩溃。在我看来,同步关键字似乎不起作用。
在应用程序一开始就从 2 个不同的线程调用此方法。一个线程从 Internet 下载并解析文件,因此它得到更新,而另一个线程(主线程)只是读取它。 (我不需要一个发生在另一个之前,因为如果它不是从互联网加载的,它只是从内部存储器中读取它)。
这是来自虚拟设备的错误吗?我的意思是,由于它是同步的(据我所知),因此不能同时调用两次。我该如何解决这个问题?
编辑:重构我的课程并使其遵循单例模式神奇地修复了它。在我拥有访问 getInstance 的公共静态方法之前,因此“我不必将 getInstance 放在我想调用此类的任何地方”。尽管如此,我将回滚我的代码以找出问题所在(我正在使用 git repo)。
Edit2:使用哈希码,我得到日志:“设置实例 1139286928 - 设置实例 1139286928 - 实例集 1139224312 - 实例集 1139287568”
Edit3:发现错误。问题在于,当他在 Resourcers 文件夹中加载文件时,方法 loadDefaults 再次使用 getInstance() 解析了该文件(调用了我拥有的 loadFromString 方法)。我以为是两个不同的线程,其实是同一个线程,为什么 synchronized 对它没有任何影响。
【问题讨论】:
-
没有人可以阻止调度程序在 getInstance() 执行完成之前切换第一个线程的上下文并让第二个线程运行。
-
您确定由于某种原因没有涉及两个不同的类加载器吗?在这种情况下,您将拥有两个独立的 Class 对象。值得在周围记录一些东西 - 例如`Log.d("StationsFile.class 哈希:" + StationsFile.class.hashCode());
-
@JonSkeet 真的很有趣,但事实并非如此。我对此进行了测试,并且在两次调用中都得到了相同的哈希码 (1139286840)。
-
鉴于这不是你的实际代码(你说过你的实际代码有一个参数),你能发布一些显示这个确切问题的代码吗?在我看来,同步真的不起作用。此外,您可以在方法中尝试
synchronized (StationsFile.class) { ... },它应该与您拥有的代码相同,但至少检查起来会很有趣。 -
非常感谢。我发现了错误,这是我的错。在第三次编辑中解释。
标签: java android thread-safety synchronized