Java中变量赋值的可见性

Java中变量赋值的可见性,java,multithreading,concurrency,Java,Multithreading,Concurrency,我最近和一个朋友就这样的代码争论: import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * See memory consistency effects in a Java Executor. */ public class PrivateFieldInEnclosing { private long value; PrivateFieldInEnclo

我最近和一个朋友就这样的代码争论:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
 * See memory consistency effects in a Java Executor.
 */
public class PrivateFieldInEnclosing {
    private long value;
    PrivateFieldInEnclosing() {}
    void execute() {
        value = initializeValue();
        ExecutorService executor = Executors.newCachedThreadPool();
        executor.submit(new Y());
    }

    class Y implements Runnable {
        @Override
        public void run() {
            System.out.println(value);
        }
    }

    private long initializeValue() {
        return 20;
    }

    public static void main(String[] args) {
        new PrivateFieldInEnclosing().execute();
    }
}
我认为有可能
value
Y
中被视为
0
,因为无法保证赋值
value=initializeValue()
在执行器的线程中可见。我说他需要把
value
设置为一个易变字段

他反驳了我,说因为它是一个私有实例字段,在创建线程之前赋值,所以这个值是可见的


我仔细研究了一下,但我无法确定我到底可以用什么作为我陈述的依据。有人能帮我吗?谢谢

不管是不是私人的都无关紧要。与此相关的是:

内存一致性影响:提交内存之前线程中的操作 执行器的可运行对象发生在其执行开始之前, 也许在另一个线程中

从。这意味着无论您在调用
submit
之前做了什么,都可以在runnable中看到。您甚至可以在构造executor之后执行它,在这种特殊情况下,执行线程何时实际启动并不重要,因为
submit
方法本身提供了非常强大的保证


这是使java.util.concurrent包非常有用的特性之一。

您的朋友是正确的。如果变量初始化在按程序顺序调用
Thread.start
之前,则根据JLS 17.4.5,它发生在
Thread.start
之前。线程的开始也发生在线程内的第一个操作之前。因此,它也发生在调用
dostufwithvalue
之前

由于使用了
Executor
:您不知道它何时调用它使用的线程的
Thread.start
,因此JLS无法单独涵盖此特定情况。但是从中可以看出,调用
submit
可以提供与
线程相同的保证。start
:在向执行器提交Runnable之前,线程中的操作发生在其执行开始之前


因为before是可传递的,所以变量初始化发生在
dostufwithvalue
之前。不过,变量作为私有实例字段的部分是不相关的。

在这种情况下,线程实际启动的时间甚至不重要,因为
submit
保证在任何情况下都会发生。因此,即使您的executor实现使用某种在JVM启动时预先启动的疯狂线程池,只要executor实现遵循API规范,您仍然是安全的。这是真的,但我选择为问题中提出的论点辩护——也就是说,从第一原则,忽略执行人提供更多担保的事实。你的回答很好地解释了executor类的观点。@Joni我不确定。如果运行
execute()。因此,在本案中需要@SergeyTachenov所指出的遗嘱执行人担保之前,会发生这种情况。“不是吗?”凯达姆哈斯韦德,在我给出答案之前,链条的哪一环发生了,你怀疑吗?我可能是wrong@Joni,从您的回答和上面的评论中可以看出,尽管使用了
executor.submit(new Runnable(){}),但是会保留before边缘我不确定我们是否可以肯定地说:-)。