【问题标题】:Synchronized static method not working in android?同步静态方法在android中不起作用?
【发布时间】: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


【解决方案1】:

试试这个...

// 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
}

你可能会明白区别是什么......

【讨论】:

  • 你也可以解释一下有什么区别
  • @blackbelt 添加了一些解释。如果你指出我这个答案是否是近似的,真的很感激......
  • 非常好的答案。它修复了崩溃,但这不是我想要的 100%。在我的例子中,我将一个参数传递给 getInstance(是的,很脏,我知道)作为构造函数中的一个可选参数。有了这个解决方案,我会做 getInstance().setParameter(param) 这不是理想的解决方案。
【解决方案2】:

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

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

并且不要像我一样在其余代码中使用静态方法来避免 getInstance()!

【讨论】:

    【解决方案3】:

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

    private static final StationsFile instance;
    

    “从 Java 6 开始,'Final' 变量具有特殊的线程安全语义,因为可以保证其他线程至少可以看到 final 字段处于其构造函数完成时所处的状态。”

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多