使用fork / join可以跨线程边界安全地移植非线程安全值吗?

时间:2018-01-11 11:21:42

标签: java parallel-processing java-stream fork-join java-memory-model

我有一些不是线程安全的类:

class ThreadUnsafeClass {
  long i;

  long incrementAndGet() { return ++i; }
}

(我在这里使用了long作为字段,但我们应该将其字段视为某些线程不安全类型。)

我现在有一个看起来像这样的课程

class Foo {
  final ThreadUnsafeClass c;

  Foo(ThreadUnsafeClass c) {
    this.c = c;
  }
}

也就是说,线程不安全类是它的 final 字段。现在我要这样做:

public class JavaMM {
  public static void main(String[] args) {
    final ForkJoinTask<ThreadUnsafeClass> work = ForkJoinTask.adapt(() -> {
      ThreadUnsafeClass t = new ThreadUnsafeClass();
      t.incrementAndGet();
      return new FC(t);
    });

    assert (work.fork().join().c.i == 1); 
  }
}

也就是说,从线程T(main),我调用T'(fork-join-pool)上的一些工作,它创建并改变我的不安全类的实例,然后返回结果包裹在Foo中。请注意,我的线程不安全类的所有变异都发生在一个线程T' 上。

问题1 :我保证thread-unsafe-class实例的结束状态安全地移植到T' ~> T的{​​{1}}线程边界上?

问题2:如果我使用并行流完成此操作会怎么样?例如:

join

1 个答案:

答案 0 :(得分:11)

我认为ForkJoinTask.join()Future.get()具有相同的记忆效应(因为它在join()中表示Javadoc基本上是get(),具有中断和异常差异)。 Future.get()specified as

  

由Future表示的异步计算所采取的操作发生在通过另一个线程中的Future.get()检索结果之后的操作之前。

换句话说,这基本上是一个安全的出版物&#34;通过Future / FJT。这意味着,FJT用户可以看到执行程序线程通过FJT.join()结果执行和发布的任何内容。由于该示例仅分配对象并在执行程序线程中填充其字段,并且在从执行程序返回后对象没有任何反应,因此我们只能看到执行程序线程生成的值。

请注意,通过final放置整个内容并不会带来任何额外的好处。即使你只是做了普通的实地商店,你仍然可以保证:

public static void main(String... args) throws Exception {
    ExecutorService s = Executors.newCachedThreadPool();
    Future<MyObject> f = s.submit(() -> new MyObject(42));
    assert (f.get().x == 42); // guaranteed!
    s.shutdown();
}

public class MyObject {
    int x;
    public MyObject(int x) { this.x = x; }
}

但请注意Stream示例(如果我们假设Stream.of.parallelExecutor.submit之间以及Stream.collectFJT.join / {{1}之间的对称性你在调用者线程中创建了对象,然后将它传递给执行者来做某事。这是一个微妙的区别,但它仍然没有多大关系,因为我们还提交了HB,这样就无法看到对象的旧状态:

Future.get

(在正式发言中,这是因为来自public static void main(String... args) throws Exception { ExecutorService s = Executors.newCachedThreadPool(); MyObject o = new MyObject(42); Future<?> f = s.submit(() -> o.x++); // new --hb--> submit f.get(); // get -->hb--> read o.x assert (o.x == 43); // guaranteed s.shutdown(); } public static class MyObject { int x; public MyObject(int x) { this.x = x; } } 的所有HB路径都是通过read(o.x)执行者线程的动作来实现的。