如何打破这个(非?)线程安全对象? [英] How can one break this (non?) thread safe object?

查看:33
本文介绍了如何打破这个(非?)线程安全对象?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

因此,我一直试图通过让数千个线程读取和写入该对象来说服自己设计已损坏(可见性) - 但我没有得到任何意外的结果.这显然不能证明它是线程安全的,可能只是证明了我自己的局限性!

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

所以我的问题是: 什么样的代码你能展示一段使用 IsItSafe 的代码,它可能导致 2 个线程查看给定 IsItSafe 实例的 bar 字段的不同值?

为了便于阅读,我将代码复制到这里:

公共类 IsItSafe 实现 Cloneable {私人 int foo;私人int吧;公共 IsItSafe foo(int foo) {IsItSafe clone = clone();克隆.foo = foo;返回克隆;}公共 IsItSafe 栏(整数栏){IsItSafe clone = clone();clone.bar = bar;返回克隆;}公共 int getFoo() {返回 foo;}公共 int getBar() {返回栏;}受保护的 IsItSafe 克隆(){尝试 {返回 (IsItSafe) super.clone();} catch (CloneNotSupportedException e) {抛出新的错误(e);}}}

解决方案

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

在给定的示例中,您没有使用 Java 提供的任何机制来确保安全发布.即在构造函数中设置的 同步volatilefinal 字段.

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

给一些参考.看这个例子

<前>类 FinalFieldExample {最终整数 x;输入 y;静态 FinalFieldExample f;公共 FinalFieldExample() {x = 3;y = 4;}静态无效作家(){f = new FinalFieldExample();}静态无效读者(){如果(f != 空){int i = f.x;int j = f.y;}}}

<块引用>

上面的类是如何使用 final 字段的示例.执行读取器的线程一定会看到 f.x 的值 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 语言规范的指导方针,您就可以保存.

I replied to a question earlier about thread safety which did not get a definite answer (I think).

So I have been trying to convince myself that the design is broken (visibility) by having thousands of threads read and write that object - but I have not been able to get anything unexpected. That is obviously not a proof that it is thread safe, probably merely a proof of my own limitations!

I understand the risk of reordering but I don't see how it could apply in that case, in the sense that the clone instance in the bar() method is local and the change on its fields is done before being released to the outside world with return, after which the instance is effectively immutable. So a thread looking at the returned object would see it with its bar field already set to the correct value...

So my question is: what kind of code Could you show a piece of code that uses IsItSafe and that could lead 2 threads to see different values of the bar field of a given instance of IsItSafe?

For reference and ease of reading I copy the code here:

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);
        }
    }
}

解决方案

Visibility problems can occur when a value your program has written to a variable is cached in the cpu cache and not written to RAM immediately. For this reason if thread A running on cpu A is writting a value without correct synchronization and thread B reads this value from cpu B he might see a stale value in RAM and not the most recent value (present only in the cpu cache of processor A).

In the example given you are using none of the mechanisms Java provides to ensure safe publication. That is synchronization, volatile or final fields set in the constructor.

Therefore one could imagine that in your example the reference to the create clone object becomes availabe but the values written to clones fields remains in the cpu cache. In this case other threads would not be able to read the up to date values.

To give some reference. Look at this example

class FinalFieldExample {
  final int x;
  int y;
  static FinalFieldExample f;
  public FinalFieldExample() {
    x = 3;
    y = 4;
  }

  static void writer() {
    f = new FinalFieldExample();
  }

  static void reader() {
    if (f != null) {
      int i = f.x;
      int j = f.y;
    }
  }
}

The class above is an example of how final fields should be used. A thread executing reader is guaranteed to see the value 3 for f.x, because it is final. It is not guaranteed to see the value 4 for y, because it is not final.

The argument you are making would hold for this example as well, wouldn't it? The instance is created, fields set in the constructor etc. However it is not thread-safe, since the value written to y needs not become visible to other threads. (The cited example is from JSR 133 (Java Memory Model) FAQ: http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#reordering)

Update: You have asked for code that demonstrates the problem. I asked a similar (more open) question once: How to demonstrate java multithreading visibility problems? The interesting thing with the code sample given is, that it will behave differently with different minor versions of Java, on different operating systems and wether using the client or server jvm. In that regard I found the sample quite interesting. What is important to note is, that it might well be impossible to actually create sample code that leads to a visibility problem with your code today. However next year cpu generation might implement a different caching policy and suddenly the problem appears. If you follow the guidelines of the Java Language Specification your save.

这篇关于如何打破这个(非?)线程安全对象?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆