如何打破这个(非?)线程安全对象?

ass*_*ias 7 java multithreading thread-safety

我之前回答了一个关于线程安全的问题,但我没有得到明确的答案(我认为).

因此,我一直试图通过让成千上万的线程读写该对象来说服自己设计被破坏(可见性) - 但我无法得到任何意外的结果.这显然不是证明它是线程安全的证据,可能只是我自己限制的证明!

我理解重新排序的风险,但我不知道它在这种情况下是如何应用的,因为方法中的clone实例bar()是本地的,并且其字段的更改在释放到外部世界之前完成return,之后实例实际上是不可变的.因此,查看返回对象的线程会看到它的bar字段已经设置为正确的值...

所以我的问题是: 什么样的代码可以显示一段使用的代码,IsItSafe并且可以导致2个线程看到bar给定实例的字段的不同值IsItSafe

为了参考和便于阅读,我在这里复制代码:

public class IsItSafe implements Cloneable {

    private int foo;
    private int bar;

    public IsItSafe foo(int foo) {
        IsItSafe clone = clone();
        clone.foo = foo;
        return clone;
    }

    public IsItSafe bar(int bar) {
        IsItSafe clone = clone();
        clone.bar = bar;
        return clone;
    }

    public int getFoo() {
        return foo;
    }

    public int getBar() {
        return bar;
    }

    protected IsItSafe clone() {
        try {
            return (IsItSafe) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new Error(e);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Joe*_*e23 4

当程序写入变量的值缓存在CPU 缓存中而不是立即写入 RAM时,可能会出现可见性问题。因此,如果在 cpu A 上运行的线程 A 在没有正确同步的情况下写入一个值,并且线程 B 从 cpu B 读取该值,他可能会在 RAM 中看到陈旧的值,而不是最新的值(仅存在于处理器 A 的 cpu 缓存中) )。

在给出的示例中,您没有使用 Java 提供的任何机制来确保安全发布。即在构造函数中设置的同步易失性最终字段。

因此,可以想象,在您的示例中,对创建clone对象的引用变得可用,但写入克隆字段的值仍保留在 cpu 缓存中。在这种情况下,其他线程将无法读取最新值。

给大家一些参考。看这个例子

类 FinalFieldExample {
  最终 int x;
  整数y;
  静态 FinalFieldExample f;
  公共 FinalFieldExample() {
    x = 3;
    y = 4;
  }

  静态无效作家(){
    f = new FinalFieldExample();
  }

  静态无效读者(){
    如果(f!= null){
      int i = fx;
      int j = fy;
    }
  }
}

上面的类是如何使用 Final 字段的示例。执行读取器的线程保证看到 fx 的值 3,因为它是最终的。不保证看到 y 的值 4,因为它不是最终的。

您提出的论点也适用于这个例子,不是吗?创建实例,在构造函数中设置字段等。但是它不是线程安全的,因为写入的值y不需要对其他线程可见。(引用的示例来自JSR 133(Java 内存模型)常见问题解答http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#reordering

更新:您已要求提供演示该问题的代码。我曾经问过一个类似的(更开放的)问题:如何演示java多线程可见性问题? 给出的代码示例的有趣之处在于,在不同的 Java 次要版本、不同的操作系统以及是否使用客户端或服务器 jvm 上,它的行为会有所不同。在这方面,我发现该示例非常有趣。需要注意的是,现在很可能不可能实际创建导致代码可见性问题的示例代码。然而,明年的 cpu 一代可能会实施不同的缓存策略,然后问题就会突然出现。如果您遵循 Java 语言规范的指导方针,您就可以省钱。

  • 一个非常好的答案!我已经删除了我的,以便这可以得到更多应有的关注:) (3认同)