方法org.osgi.framework.Version.toString()是否线程安全?

3

org.osgi.framework.Version.toString()方法已知会导致性能问题(详见Bug 324331 - redundant Strings created from Version.toString)。为了解决这个问题,该方法已经做了更改,现在采用延迟初始化和数据竞争(可能是为了提高性能)。

// OSGi Service Platform Release 4 Version 4.3 Core Companion Code 
public String toString() {
    if (versionString != null) {
        return versionString;
    }
    int q = qualifier.length();
    StringBuffer result = new StringBuffer(20 + q);
    result.append(major);
    result.append(SEPARATOR);
    result.append(minor);
    result.append(SEPARATOR);
    result.append(micro);
    if (q > 0) {
        result.append(SEPARATOR);
        result.append(qualifier);
    }
    return versionString = result.toString();
}

我认为这段代码不是线程安全的,因为versionString字段的读取可能会被重新排序,使得方法返回null值。我是否正确?或者说这没关系,因为只有在适当同步的情况下才会调用它?根据JLS第17章关于并发性的作者之一Jeremy Manson在这篇博客文章中的说法,实际上可能会发生这种情况。

1
我是这段代码的作者。通过Jeremy Manson的博客文章,我可以看出这里存在潜在,即便很小的问题。我将更新这段代码(和R6的hashCode)以避免此类问题。谢谢。 - BJ Hargrave
1个回答

2

这更多是关于缓存而不是懒惰。

但你是对的,根据Java内存模型,它可以返回null。

StringversionString;

public String toString() {
    if (versionString != null) {
        return versionString;  // can return null here!!
    }

理论上,它可以转换为:
    String tmp1 = versionString;  // reads null
    String tmp2 = versionString;  // reads non-null
    if(tmp2!=null)
        return tmp1;              // return null!

然而,实际上没有任何JVM会这样做,因此这个bug可能永远不会出现。

尽管如此,做的“正确”的事情是

public String toString() {
    String tmp = versionString;
    if (tmp != null) {
        return tmp;
    }

尽管最后一行没问题

    return versionString = result.toString();

它不会读取 versionString,等同于

    String tmp3 = result.toString();
    versionString = tmp3;
    return tmp3;

有趣的是,即使我们这样做
    versionString = result.toString();   // [w]
    return versionString;                // [r]

它仍然是安全的。最后一次读取不应返回null,因为[w] happens-before [r]。


我不确定我同意。第一个if语句返回null的唯一方式是在if评估和return执行之间,另一个线程分配了null,对吧?正如提供的示例所示,假设没有其他代码修改versionString,则永远不会分配null。 - David Welch

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接