我有一些不是线程安全的类:
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
答案 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.parallel
和Executor.submit
之间以及Stream.collect
和FJT.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)
执行者线程的动作来实现的。