我有这样的场景(这是Java伪代码):
有一个主线程:
1)创建一个C类型数组的实例:
C[] arr = new C[LARGE];
2)创建并提交填充(通过CPU绑定操作)arr到池P1的任务:
for (int i = 0; i < populateThreadCount; i++) {
p1.submit(new PopulateTask(arr, start, end))
}
每个任务在arr中填充不同的索引范围,因此此时池P1中的线程之间不需要同步。
3)主线程等待所有填充任务完成。
4)一旦arr被填充,主线程就会创建并提交上传(IO绑定操作)arr内容的任务到池P2:
for (int i = 0; i < uploadThreadCount; i++) {
p2.submit(new UploadTask(arr, start, end);
}
如前所述,范围不重叠,每个线程都有自己的范围,因此P2池中的线程之间不需要内部同步。
在填充和上传任务中,范围是不同的,因为处理每种类型的线程数不同。
现在我在想什么是同步它的最有效方法。
使用CopyOnWriteArrayList不是一个选项,因为它可能非常大(数百万个元素)。
我最初的想法是在创建C类实例之后在填充任务中简要同步,然后在上传任务中同步:
C[] arr = new C[LARGE];
for (int i = 0; i < populateThreadCount; i++) {
p1.submit(new PopulateTask(arr, start, end) {
void run() {
for (int j = start; j <= end; j++) {
... do some heavy computation ...
arr[j] = new C(some_computed_data);
synchronized(arr[j]) {}
}
}
});
}
for (int i = 0; i < uploadThreadCount; i++) {
p2.submit(new UploadTask(arr, start, end) {
void run() {
for (int j = start; j <= end; j++) {
synchronized(arr[j]) {
upload(arr[j]);
}
}
}
});
}
但不确定这是否正确,特别是如果这个空的同步块不能被javac或JIT优化。 在开始填充任务之前,我无法创建C类的实例,因为我需要计算数据。
任何想法,如果这是正确的,如果不是更好的方法吗?
答案 0 :(得分:2)
您不需要同步任何内容。执行程序提供您需要的内存可见性保证。特别是,请参阅concurrent package documentation:
- 在执行Runnable之前,线程中的操作发生在执行开始之前。类似地,提交给ExecutorService的Callables也是如此。
- 由Future表示的异步计算所采取的操作发生在通过另一个线程中的Future.get()检索结果之后的操作之前。
因此,提交给第一个执行程序的任务所做的更改发生在执行程序完成执行之后主线程所执行的更改(第二个规则)之前,主线程对数组执行的操作发生在执行操作之前提交给第二执行者的任务(第一条规则)。
由于before-before是传递性的,因此提交给第二个执行者的任务将看到提交给第一个执行者的任务所做的更改。