Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/multithreading/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 具有非final字段的不可变对象怎么可能是线程不安全的?_Java_Multithreading_Concurrency_Java Memory Model - Fatal编程技术网

Java 具有非final字段的不可变对象怎么可能是线程不安全的?

Java 具有非final字段的不可变对象怎么可能是线程不安全的?,java,multithreading,concurrency,java-memory-model,Java,Multithreading,Concurrency,Java Memory Model,说我们有这个 // This is trivially immutable. public class Foo { private String bar; public Foo(String bar) { this.bar = bar; } public String getBar() { return bar; } } 是什么使这个线程不安全?从这里开始。由于JVM优化,您永远不能假设操作是按照写入的顺序执行的,除非这对

说我们有这个

// This is trivially immutable.
public class Foo {
    private String bar;
    public Foo(String bar) {
        this.bar = bar;
    }
    public String getBar() {
        return bar;
    }
}

是什么使这个线程不安全?从这里开始。

由于JVM优化,您永远不能假设操作是按照写入的顺序执行的,除非这对同一线程很重要。因此,当您调用构造函数,然后将对结果对象的引用传递给另一个线程时,JVM可能不会在同一线程中需要foo.bar的值之前写入它


这意味着在多线程环境中,可以在将构造函数中的值写入getBar方法之前调用它。

Foo
一旦安全发布,它就是线程安全的。例如,此程序可能会打印“不安全”(可能不会使用hotspot/x86的组合)-如果将
bar
final设置为final,则不会发生:

public class UnsafePublication {

    static Foo foo;

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (foo == null) {}
                if (!"abc".equals(foo.getBar())) System.out.println("unsafe");
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                foo = new Foo("abc");
            }
        }).start();
    }
}
从发布的评论中:

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;  // guaranteed to see 3  
            int j = f.y;  // could see 0
        } 
    } 
}

一个线程可以调用
writer()
,另一个线程可以调用
reader()
。reader()中的if条件可以计算为true,但由于y不是最终的,对象初始化可能尚未完全完成(因此该对象尚未安全发布),因此
int j=0
可能会发生,因为它尚未初始化。

很可能您现在已经得到了答案,但为了确保我想补充我的解释

为了使对象(对于您的案例)是线程安全的,它必须:

  • 一成不变
  • 安全出版
不可变的-是你创造的。一旦设置了该栏,就无法修改它。这里很明显

安全发布。根据示例,代码未安全发布。由于bar不是最终的,编译器可以根据需要对其重新排序。编译器可以在write-to-bar之前发布(写入主存)对Foo实例的引用。这意味着该条为空。因此,首先将对Foo的引用写入主存,然后写入bar。在这两个事件之间,另一个线程可以看到过时的条为空

如果您向其添加final,JMM将保证:

最终字段的值保证对访问构造对象的其他线程可见


或者,最后一个字段阻止重新排序。因此,将该变量设为final将确保线程安全。

该问题的答案不包含在该问题的答案中吗?特别是,read看到了你对我的问题的评论,我很好奇答案可能是重复的:@NoobUnChained绝对不是重复的。这里没有setter。@Jonsket向我提出了另一个问题,描述了final如何防止数据争用问题,但没有涉及可能出现的问题-线程看到由于JVM内存模型重新排序而部分初始化的对象,等等,在这种情况下,getBar()会是什么return?@NoobUnChained因为所有引用实例变量都有一个默认值
null
,应该是这样。如果
String bar
还没有赋值,那么
foo.getBar()
怎么办?@assylias但是
foo.getBar()
可能仍然返回
null
,不是吗?@Eugene,仅仅将字段设置为final并不能保证安全发布,相反,我们仍然必须确保在对象的构造函数完成之前,不要在另一个线程可以看到的地方写入对正在构造的对象的引用。所以在这里,如果我们将bar字段设为final,那么当thread2在foo的构造函数完成后更新foo时,thread1方法将保证看到bar的正确初始化值,即foo.getBar()将永远不会返回null.So。。最后一个赋值是原子的?@Eugene,在“最近的”JVM(包括64位字段)中,每个赋值都是原子的。@BrunoReis true。我的意思是:如果y也是final(在上面的示例中),这是否意味着这两个变量都以原子方式设置为相应的值?或者更简单一点:这是否意味着构造函数是原子线程安全的?没有其他线程可以看到过时的值x或/和y?@Eugene:如果对象仅在构造函数返回后才发布(即,您没有从构造函数内部将
传递给任何外部对象),那么所有最终字段都保证由每个获取该对象引用的线程初始化。