gpt4 book ai didi

java - 能否使用 fork/join 跨线程边界安全地移植非线程安全值?

转载 作者:塔克拉玛干 更新时间:2023-11-03 04:54:41 26 4
gpt4 key购买 nike

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

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(主线程),我调用了 T'(fork-join-pool)上的一些工作,它创建并改变了我的实例unsafe 类,然后返回包装在 Foo 中的结果。请注意,我的线程不安全类的所有突变都发生在单个线程 T' 上。

问题 1:我是否保证 thread-unsafe-class 实例的结束状态安全地跨过 T' ~> T 线程边界在 join?

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

Map<Long, Foo> results = 
Stream
.of(new ThreadUnsafeClass())
.parallel()
.map(tuc -> {
tuc.incrementAndGet();
return new Foo(tuc);
})
.collect(
Collectors.toConcurrentMap(
foo -> foo.c.i,
Function.identity();
)
);
assert(results.get(1) != null)

最佳答案

我认为 ForkJoinTask.join()Future.get() 具有相同的内存效果(因为它在 join() 中说Javadoc 基本上是 get(),但有中断和异常差异)。而 Future.get()specified as :

Actions taken by the asynchronous computation represented by a Future happen-before actions subsequent to the retrieval of the result via Future.get() in another thread.

换句话说,这基本上是通过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/Future.get),您已经在调用线程中创建了对象,然后将其传递给执行者做某事。这是一个细微的差别,但仍然没有太大关系,因为我们在提交时也有 HB,这会阻止看到对象的旧状态:

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; }
}

(正式地说,这是因为所有来自read(o.x) 的 HB 路径都经过执行器线程执行store(o.x, 43) 的操作)

关于java - 能否使用 fork/join 跨线程边界安全地移植非线程安全值?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48205902/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com