【问题标题】:Thread safety of a fluent like class using clone() and non final fields使用 clone() 和非 final 字段的流式类的线程安全
【发布时间】:2023-03-19 11:53:01
【问题描述】:

这个类似流利的类并不是严格不可变的,因为字段不是最终的,但它是线程安全的,为什么?

我关心的线程安全问题不是竞争条件,而是变量的可见性。我知道有一种使用最终变量和构造函数而不是 clone() + 赋值的解决方法。我只是想知道这个例子是否可行。

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

【问题讨论】:

  • 您可以通过将 IsItSafe 设置为最终版本来稍微安全一些。您是否担心有人通过反射改变 foo 或 bar?您为什么担心他们的知名度?
  • 我不担心有人通过反射或扩展类来修改字段。我问是因为我设计了一个类似的类,它的线程安全性受到了质疑。
  • 仅供参考,我在这里发布了问题的延续:stackoverflow.com/questions/9633771/…

标签: java thread-safety fluent-interface


【解决方案1】:

您在设置字段时没有持有锁,并且当您提到自己时,该字段不是最终的。

因此,从可见性的角度来看,这种方法不是线程安全的。

这里有一些进一步的澄清:https://stackoverflow.com/a/9633968/136247

关于使用 volatile 问题的更新:

为了论证,使用 volatile 修复了这里的线程问题。
但是,您应该重新考虑 final 字段和复制构造函数,因为:

  • 字段访问会稍微快一些(读取总是来自 cpu 缓存)
  • 避免不鼓励使用 clone(参见 Josh Bloch 的 Effective Java)
  • 具有 final 字段的构造函数是不可变类的已知习惯用法,代码读者很容易识别
  • 标记字段 volatile 同时打算让它们不可变本身就是矛盾的 ;)

【讨论】:

  • 我不确定您所说的 逻辑 是什么意思。你的意思是实用?或者你的意思是如果没有 Java 内存模型并且这是伪代码?
  • 所以将字段标记为 volatile 可以解决可见性问题,对吧?
  • @EmmanuelBourg 根据Joe23分享的链接,是的:cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#volatile
  • 答案已接受,感谢您在相关问题 Joe 中提供的详细信息,这正是我想要的。
【解决方案2】:

thread 相当一致地认为,由于可见性问题,该类不是线程安全的。

为什么说类不是不可变的?类的状态由 foo 和 bar 定义,对于任何特定实例,一旦创建实例,就不能从类外部更改它们。所以它是不可变的,即使字段没有明确声明为 final。

更改 foo 和 bar 的唯一地方(在 foo() 和 bar() 方法中),更改是在局部变量上完成的,根据定义,该局部变量一次只能由一个线程访问。

编辑

我认为这是 Java Concurrency in Practice (3.3.2) 中定义的 Stack Confinement 示例,它使 foo() 和 bar() 方法线程安全,因为之前不允许 clone 转义该方法完全建成。

局部变量本质上仅限于执行的threead;它们存在于执行线程的堆栈中,其他线程无法访问。

【讨论】:

  • 这就是我的想法,但我有一个疑问。如果 foo() 或 bar() 返回的实例与另一个线程共享,它会看到 bar 或 foo 的更新值吗?
  • (我不是反对者。)关于您的不可变论点:您是否考虑过指令重新排序?如果您在此处所说的是正确的,则不需要 final 修饰符来创建线程安全的不可变对象。关于您的堆栈限制参数:堆栈局部变量是clone。可见性问题涉及foobar 字段,它们显然不是堆栈本地的。
  • +1,感谢您为这个问题付出的时间,谢谢 assylias!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-30
  • 2011-11-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-18
相关资源
最近更新 更多