使用clone()和非final字段对类似流畅的线程安全性

时间:2012-03-08 16:35:37

标签: java thread-safety fluent-interface

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

我关注的线程安全问题不是竞争条件,而是变量的可见性。我知道有一个解决方法使用final变量和构造函数而不是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);
        }
    }
}

2 个答案:

答案 0 :(得分:1)

由于可见性问题,此thread非常一致,因为该类不是线程安全的。

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

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

修改

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

  

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

答案 1 :(得分:1)

您在设置字段时没有按住锁定,正如您自己提到的那样,该字段不是最终字段。

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

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

有关使用volatile的问题的更新:

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

  • 字段访问速度稍快(读取总是来自cpu缓存)
  • 您可以避免使用clone(请参阅Josh Bloch的Effective Java)
  • 带有final字段的构造函数是不可变类的已知习惯用法,很容易被代码读者识别
  • 标记字段volatile,同时打算使它们不可变,这本身就是一个矛盾;)