Java 同步静态方法在android中不起作用?

Java 同步静态方法在android中不起作用?,java,android,thread-safety,synchronized,Java,Android,Thread Safety,Synchronized,我有一个班级需要单身: 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(

我有一个班级需要单身:

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
    }
}
现在,由于这个方法是同步的,而且是静态的,所以应该一次只调用一次。当我在我的设备(安卓4.4)中运行它时,没有问题(在我的日志中,我得到了“set instance-instance set”),但是当我第一次在安卓2.2虚拟设备中运行它时(并且只有在安装后的第一次),或者每次在安卓4.4虚拟设备中运行它时,我得到了我的日志“set instance-set instance-instance set-instance set-instance set”,以及由此引发的一场车祸

因此,当启动“缓慢”时,它看起来会崩溃。在我看来,synchronized关键字似乎不起作用

此方法在应用程序的一开始就从两个不同的线程调用。一个线程从internet下载并解析文件,因此它会得到更新,而另一个线程(主线程)只是读取它。(我不需要一个先发生另一个,因为如果它不是从internet加载的,它只是从内存中读取)

这是来自虚拟设备的bug吗?我的意思是,因为它是同步的(据我所知),所以不能同时调用两次。我怎样才能解决这个问题

编辑:折射我的类并使其遵循单例模式神奇地修复了它。在我有访问getInstance的公共静态方法之前,“我不必把getInstance放在我想调用这个类的任何地方”。尽管如此,我还是要回滚代码以找出问题所在(我使用的是git回购)

Edit2:使用hashcode,我得到日志:“set instance 1139286928-set instance 1139286928-instance set 1139224312-instance set 1139287568”

Edit3:找到了bug。问题是loadDefaults方法在Resources文件夹中加载文件时,再次使用getInstance()解析该文件(调用我使用的loadFromString方法)。我以为它是两个不同的线程,但它是同一个线程,因此为什么同步对它没有任何影响。

试试这个

// initialized when the class is loaded for the first time
private static final StationsFile instance = new StationFile(); 

public static StationsFile getInstance() {
   return instance; // no need of null check here. No worry about synchronization
}

您可能会得到不同之处…

因此,正如我在编辑中所说的,问题是因为我从同一个方法调用了同步方法。这听起来可能像是递归,但在本例中不是,因为有一个可选参数会产生其他效果

如果得到相同的结果,只需在synchronized方法中打印/记录,以检查它是否跟踪到对同一方法的另一个调用


不要像我一样在代码的其余部分使用静态方法来避免getInstance()

只需将实例变量设置为final即可解决问题:

private static final StationsFile instance;

“从Java 6中,‘Final’变量具有特殊的线程安全语义,因为其他线程保证至少看到Final字段在其构造函数完成时的状态。”

没有人可以阻止调度程序切换第一个线程的上下文,并让它在getInstance()之前运行第二个线程已完成执行。您确定没有因为某种原因涉及两个不同的类加载器吗?在这种情况下,您将有两个独立的类对象。在这一点上记录一些东西是值得的——例如,`Log.d(“StationsFile.class散列:”+StationsFile.class.hashCode())@JonSkeet真的很有趣,但事实并非如此。我对此进行了测试,在两次调用(1139286840)中都得到了相同的哈希代码。鉴于这不是您的实际代码(您已经说过您的实际代码有一个参数),您能否发布一些显示此问题的代码?在我看来,同步真的不起作用似乎不大可能。另外,您可以尝试在方法中使用
synchronized(StationsFile.class){…}
,这应该与您获得的代码相同,但至少检查起来很有趣。非常感谢。我发现了窃听器,是我的错。在第三次编辑中解释。您也可以非常友好地解释difference@blackbelt补充了一些解释。非常感谢你告诉我这个答案是否是近似的…非常好的答案。它修复了崩溃,但这不是我想要的100%。在我的例子中,我将为构造函数中的可选参数向getInstance传递一个参数(是的,我知道,非常脏)。对于这个解决方案,我要做的是getInstance().setParameter(param),这不是理想的解决方案。