Streams和fork-join都提供了并行化访问数组的代码的功能。例如,Arrays.parallelSetAll
主要由以下行实现:
IntStream.range(0, array.length).parallel()
.forEach(i -> { array[i] = generator.applyAsLong(i); });
此外,{ - 1}} RecursiveAction
是fork-join框架的一部分,包含以下示例:
static class SortTask extends RecursiveAction {
final long[] array; final int lo, hi;
...
void merge(int lo, int mid, int hi) {
long[] buf = Arrays.copyOfRange(array, lo, mid);
for (int i = 0, j = lo, k = mid; i < buf.length; j++)
array[j] = (k == hi || buf[i] < array[k]) ?
buf[i++] : array[k++];
}
}
最后,从数组创建的并行流访问多个线程中的数组(代码太复杂,无法在此汇总)。
所有这些示例似乎都是对数组进行读取或写入,而没有任何同步或其他内存障碍(据我所知)。我们知道,完全临时的多线程数组访问是不安全的,因为不能保证读取反映另一个线程中的写入,除非读取和写入之间存在先发生关系。实际上,Atomic...Array
类是专门为解决此问题而创建的。但是,鉴于上面的每个例子都在标准库或其文档中,我认为它们是正确的。
在这些例子中,有人可以解释一下哪种机制可以保证数组访问的安全性?
答案 0 :(得分:6)
简短回答:分区。
JMM是根据对变量的访问权限定义的。变量包括静态字段,实例字段和数组元素。如果你安排你的程序,使得线程T0是访问数组元素0的唯一线程,同样T1是访问数组元素1的唯一线程,那么这些元素中的每一个都是有效线程限制的,并且你有没问题 - JMM程序订单规则会照顾你。
并行流建立在这个原则之上。每个任务都在处理数组的一部分,而其他任务正在处理。然后我们要做的就是确保运行任务的线程可以看到数组的初始状态,最终结果的使用者可以看到数组相应部分的as-modified-by-the-task视图。这些可以通过嵌入在并行流和FJ库的实现中的同步动作轻松安排。