【问题标题】:Visibility of assignment to variable in JavaJava中变量赋值的可见性
【发布时间】:2016-06-11 12:24:41
【问题描述】:

我最近和一个朋友就这样的代码发生了争执:

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 成为一个易变字段。

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

我查看了https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4,但我无法确定我可以使用什么来支持我的声明。谁能帮我?谢谢!

【问题讨论】:

    标签: java multithreading concurrency


    【解决方案1】:

    它是否是私人的无关紧要。相关的是:

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

    来自Executor docs。这意味着在调用 submit 之前所做的任何事情都可以在可运行文件中看到。你甚至可以在构造执行器之后执行它,在这种特殊情况下,执行线程实际上何时启动都无关紧要,因为submit 方法本身就提供了非常强大的保证。

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

    【讨论】:

      【解决方案2】:

      你的朋友是对的。如果变量初始化是在程序顺序中调用 Thread.start 之前,则在 JLS 17.4.5 中它发生在 Thread.start 之前。线程的开始也发生在线程内的第一个动作之前。因此它也发生在调用doStuffWithValue之前。

      由于使用了Executor,JLS 无法单独涵盖这种特殊情况:您不知道它何时为它使用的线程调用Thread.start。但是从here 你可以读到调用submit 为你提供了与Thread.start 相同的保证:在将可运行对象提交给执行程序之前,线程中的动作发生在执行开始之前。

      由于happens-before 是传递性的,变量初始化happens-before doStuffWithValue。不过,关于变量是私有实例字段的那一点无关紧要。

      【讨论】:

      • 在这种情况下,线程实际启动的时间甚至都不重要,因为submit 保证无论如何都发生在之前。因此,即使您的执行器实现使用了在 JVM 启动时预先启动的某种疯狂线程池,只要执行器实现遵循 API 规范,您仍然是安全的。
      • 没错,但我选择为问题中提出的论点辩护——也就是说,从第一原则出发,忽略了执行者提供更多保证的事实。您的回答很好地解释了执行者类的观点。
      • @Joni 我不确定。如果运行execute() 方法的线程执行new Thread(new Y()).start() 之类的操作,那么我认为value 在启动的Runnable 中不可见,而valuevolatile。因此,在这种情况下,需要 @SergeyTachenov 指出的 Executors 的发生前保证。没有?
      • @KedarMhaswade 我在答案中出现的先发生链的哪个环节您怀疑吗?我可能错了
      • @Joni,从您的回答和上面的评论中可以看出,尽管使用了 executor.submit(new Runnable() {});,但之前发生的边缘将被保留,我不确定我们是否可以肯定地说: -)。
      猜你喜欢
      • 2015-07-06
      • 1970-01-01
      • 2011-01-24
      • 1970-01-01
      • 2011-10-08
      • 1970-01-01
      • 1970-01-01
      • 2012-12-01
      • 2020-12-20
      相关资源
      最近更新 更多