Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/382.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
连续并行流之间的Java缓存一致性?_Java_Caching_Volatile - Fatal编程技术网

连续并行流之间的Java缓存一致性?

连续并行流之间的Java缓存一致性?,java,caching,volatile,Java,Caching,Volatile,考虑下面这段代码(乍一看并不是这样) 静态类号容器{ int值=0; 无效增量(){ 值++; } int getValue(){ 返回值; } } 公共静态void main(字符串[]args){ 列表=新的ArrayList(); 整数=100000; 对于(int i=0;i

考虑下面这段代码(乍一看并不是这样)

静态类号容器{
int值=0;
无效增量(){
值++;
}
int getValue(){
返回值;
}
}
公共静态void main(字符串[]args){
列表=新的ArrayList();
整数=100000;
对于(int i=0;i{
if(container.getValue()!=numIterations){
System.out.println(“问题!!!”;
}
});
}
我的问题是:为了绝对确定不会打印“Problem!!!”,NumberContainer类中的“value”变量是否需要标记为volatile?

让我解释一下我目前是如何理解这一点的

  • 在第一个并行流中,NumberContainer-123(say)由ForkJoinWorker-1(say)递增。因此,ForkJoinWorker-1将具有NumberContainer-123.value的最新缓存,即1。(但是,其他fork-join工作线程将具有过时的NumberContainer-123.value缓存-它们将存储值0。在某些时候,这些其他工作线程的缓存将被更新,但这不会立即发生。)

  • 第一个并行流完成,但公共fork-join-pool工作线程不会被终止。然后,第二个并行流使用相同的公共fork-join池工作线程启动

  • 现在,假设在第二个并行流中,递增NumberContainer-123的任务被分配给ForkJoinWorker-2(比如)。ForkJoinWorker-2将有自己的缓存值NumberContainer-123.value。如果NumberContainer-123的第一次和第二次增量之间经过了很长一段时间,则NumberContainer-123.value的ForkJoinWorker-2缓存将是最新的,即将存储值1,并且一切正常。但是,如果NumberContainer-123非常短,那么第一次增量和第二次增量之间经过的时间会怎样?那么,ForkJoinWorker-2的NumberContainer-123.value缓存可能已过期,存储了值0,导致代码失败


我上面的描述正确吗?如果是这样的话,有人能告诉我两个递增操作之间需要什么样的时间延迟才能保证线程之间的缓存一致性吗?或者如果我的理解是错误的,那么有人能告诉我是什么机制导致线程本地缓存在第一个并行流和第二个并行流之间被“刷新”吗?

它应该不需要任何延迟。当您离开
ParallelStream
forEach
时,所有任务都已完成。这将在增量和
forEach
的结尾之间建立一个发生在前面的关系。所有的
forEach
调用都是通过从同一线程调用来排序的,类似地,检查在所有
forEach
调用之后进行

int numIterations = 10000;
for (int j = 0; j < numIterations; j++) {
    list.parallelStream().forEach(NumberContainer::increment);
    // here, everything is "flushed", i.e. the ForkJoinTask is finished
}
int numIterations=10000;
对于(int j=0;j
回到你关于线程的问题,这里的诀窍是,线程是不相关的。内存模型取决于before-before关系,fork-join任务确保对
forEach
的调用与操作体之间的before-before关系,以及操作体与来自
forEach
的返回之间的before-before关系(即使返回值为
Void

另见

正如@erickson在评论中提到的

如果你不能通过关系之前发生的事情来确定正确性, 没有多少时间是“足够的”;你 需要正确应用Java内存模型

此外,从“刷新”记忆的角度来思考是错误的,因为还有很多事情会影响你。例如,刷新很简单:我没有检查,但可以打赌任务完成时只存在一个内存障碍;但是您可能会得到错误的数据,因为编译器决定优化非易失性读取(变量不是易失性的,并且在这个线程中没有更改,所以它不会更改,所以我们可以将它分配到寄存器,等等),以发生之前关系允许的任何方式重新排序代码,等等

最重要的是,所有这些优化都会随着时间的推移而改变,因此,即使您转到生成的程序集(根据负载模式可能会有所不同)并检查所有内存障碍,也不能保证代码能够正常工作,除非您能证明读操作发生在写操作之后,在这种情况下,Java内存模型站在您这边(假设JVM中没有bug)


至于巨大的痛苦,
ForkJoinTask
的目标就是使同步变得微不足道,所以请享受。它(似乎)是通过标记
java.util.concurrent.ForkJoinTask#status
volatile来完成的,但这是一个您不应该关心或依赖的实现细节。

当我只有80%的把握知道答案时,我讨厌它。:-)让我直截了当地说,如果它被标记为易变的,那将如何改变事情呢?volatile是关于后续操作的,有人向volatile写信,有人必须向观察者写信;这就是以前发生的事情。因此,简单地添加volatile并不能解决问题,以防出现问题broken@Eugene如果NumberContainer-123.value被标记为volatile,那么将在写入值1的第一个并行流和读取值的第二个并行流之间建立一个before关系。(叉子连接着一个工人
int numIterations = 10000;
for (int j = 0; j < numIterations; j++) {
    list.parallelStream().forEach(NumberContainer::increment);
    // here, everything is "flushed", i.e. the ForkJoinTask is finished
}