Java内存模型对于线程池交互的发生顺序保证

11
Java内存模型是否为线程池交互提供happens-before保证?具体而言,线程池工作线程在运行完工作队列中的项目之前所做的写入是否对运行该队列下一个项目的工作线程可见?
规范(我个人认为这个FAQ很有用:http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#synchronization)指出:“对线程调用start()发生在启动的线程中的任何操作之前。”或者简单地说,在启动线程之前进行的任何内存写入都将在启动的线程执行的run()方法之前被执行并且可见。但是对于线程池来说,start()通常会在你进行写入之前运行。考虑一个简单的工作流,其中上下文对象被改变并传递到下一个操作:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {

    private static class Container<T> {
        private T value;
        public T get() {
            return value;
        }
        public void set(T newValue) {
            value = newValue;
        }
    }

    public static void main(String[] args) {
        final Container<Integer> sharedObject = new Container<>();
        final ExecutorService executor = Executors.newFixedThreadPool(10);
        // SKIPPED: pre-warm the executor so all worker threads are start()'ed
        final Runnable read = () -> System.out.println("Got " + sharedObject.get());
        Runnable write = () -> {
            sharedObject.set(35);
            executor.execute(read);
        };
        executor.execute(write);
        // SKIPPED: wait until done
    }
}
< p > write.run() sharedObject.value 的写入能够被 read.run() 所看到吗?(不是询问顺序,这是显而易见的)

(PS:我知道将 value 设为 volatile 可以提供这个保证)

更新(补充回答): java.util.concurrent 包的摘要文档总结了语言提供并由框架扩展的内存一致性保证:https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/package-summary.html#MemoryVisibility

2个回答

10

我认为这是有保证可见性的。 ExecutorService 扩展了 Executor,而 Executorjavadocs表示:

内存一致性效应:在向 Executor 提交 Runnable 对象之前的线程中执行的操作,在其执行开始时与另一个线程中执行的操作具有先于关系。

根据我的理解,这与您的示例中正在发生的事情相匹配。 write 可运行对象正在提交 read 可运行对象,因此在 write 线程中提交之前和之后在 read 线程中执行的事件之间存在 先于 关系(即 set 调用之前的事件和 get 调用之后的事件)。

事实上,write 可运行对象本身被提交意味着创建 Container 对象并调用 set 之间也存在 先于 关系。


2
引用ExecutorService的javadoc:
内存一致性效果:在将RunnableCallable任务提交给ExecutorService之前,线程中的操作会先于该任务执行的任何操作发生,而该任务执行的操作又会先于通过Future.get()检索结果发生。
但是,它并没有说明两个添加到队列中的任务之间是否有happen-before关系,以及任务1的处理是否在任务2的处理之前发生,从任务的角度来看。只是添加任务到队列中发生在任务处理之前,并且任务执行发生在原始调用者检索结果之前。
更新:
即使某种方式知道一个任务在另一个任务开始运行之前已经完成运行,也没有两个不同、独立提交的任务之间的happen-before关系。
当然,在一个任务提交另一个任务时,就像在问题中所做的那样,任务1在提交任务2之前执行的任何操作都将发生在任务2的执行之前。
如果任务1在提交任务2后继续执行其他操作,则当然没有happen-before保证,因为任务2可能在任务1继续工作之前运行并完成。

2
我不明白你的意思 - 你的结论与你引用的语句相反...在op的例子中,sharedObject.set(35)println之间肯定存在hb关系。也许你错过了第二个任务是从第一个任务中添加到队列中的这一事实,而不是在主线程中添加的。 - assylias
@assylias 你说得对,我确实忽略了这个事实。正如我的回答所暗示的那样,我谈论的是两个独立添加的任务,而不是一个任务提交另一个任务。 - Andreas
感谢您注意到我的问题和示例之间的不匹配。示例是我想了解但无法弄清楚问题的内容,它应该是:在提交“读取”的线程之前,在“写入”中进行的写入操作是否对“读取”的线程可见。 - Pavel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接