Conway'的多线程Java程序;s的生命游戏——边境牢房的争夺

Conway'的多线程Java程序;s的生命游戏——边境牢房的争夺,java,multithreading,synchronization,conways-game-of-life,java-memory-model,Java,Multithreading,Synchronization,Conways Game Of Life,Java Memory Model,我正在学习java并发编程,并为《生活游戏》编写一个模拟程序 以下是我的想法: 使用int[][]存储单元格的状态 将int[][]划分为t段,并使用t工作线程 t线程将从其段中读取数据,为其段中的所有单元格计算新值并更新单元格 一旦他们完成计算,他们就在栅栏前等待其他工人完成 当越过障碍时,主线程将更新UI 工人们继续计算下一个状态 现在,将在段的公共边界上存在争用。如果线程在其邻居读取上一个值之前重写了边界单元的状态,则邻居的计算将出错 我有什么选择 使用callable而不是runn

我正在学习java并发编程,并为《生活游戏》编写一个模拟程序

以下是我的想法:

  • 使用int[][]存储单元格的状态
  • 将int[][]划分为t段,并使用t工作线程
  • t线程将从其段中读取数据,为其段中的所有单元格计算新值并更新单元格
  • 一旦他们完成计算,他们就在栅栏前等待其他工人完成
  • 当越过障碍时,主线程将更新UI
  • 工人们继续计算下一个状态
现在,将在段的公共边界上存在争用。如果线程在其邻居读取上一个值之前重写了边界单元的状态,则邻居的计算将出错

我有什么选择

  • 使用callable而不是runnable,并让工作线程返回新值(而不是更新段本身)。主线程可以在跨越屏障后更新矩阵。此选项涉及将工作线程返回的结果复制到矩阵中
  • 使用两个屏障。工作线程从相邻的段复制边界单元,并在第一个屏障处等待。一旦通过此屏障,他们将继续计算下一个状态,并在适当的位置更新分段。然后他们在第二道屏障处等待。主线程更新UI
我的问题是,有没有其他方法来处理边界单元格的争用不涉及复制数据,或者比上述两种方法更有效?可能正在使用ReaderWriterLock、volatile变量或其他同步机制


更新:到目前为止,这是最干净的。但我有一个问题由于这两个阵列是共享数据,并且我们没有使用任何同步(同步访问或可变变量),这会不会造成可见性问题?多个CPU能否缓存数组值,并在每次迭代中仅更新数组的一部分?然后线程将获得边框单元格的过时值。这可能吗?若否,原因为何。如果是,如何解决?看起来。

这并不能回答你的实际问题,但我的建议将是你的第一选择。。。返回新值,而不是让工作线程更新它们。我会更进一步,让“聚合器”将工作线程的结果合并到一个新的板状态数组中,然后丢弃第一个。我的理由是,它将提高逻辑的整体可读性,因为您几乎不必担心“全局交互”


也就是说,我有点偏颇,因为我更喜欢以功能性的方式编程,除非有充分的理由不这样做。

我会尝试以下方法:

  • 让工作人员执行计算,但只将值写回内部单元格
  • 对于边框单元格,存储结果
  • 计算完成后,在屏障处等待
  • 当所有工人都在第一道屏障上时,释放并允许每个工人写入边界单元格
  • 当UI更新时,在第二个屏障处等待

n x m
磁贴所需的存储空间为
2*(n+m-1)
,因此通常较大的磁贴(8x8或更多)所需的缓存值内存会相应减少。

我建议使用2个int[][]数组。让我们称它们为A和B。A将保存所有奇数“记号”的值,B将保存偶数“记号”

将初始化为初始状态。然后让线程释放以计算每个单元格的下一个状态,并将结果放在B中相应的单元格中。 一旦所有线程都完成了,您的新状态就在B中。现在,使用B计算每个单元格的下一个状态,并将结果存储在A中。在任何给定时间,一个数组将是只读的,另一个数组将是只读的,因此不会有任何争用

优点:

  • 与您现在所做的相比,没有数据复制。每个单元仅发生一次写入
  • 由于算法简单,无需担心边缘/角点情况
  • 没有正在进行的内存分配。开始时只需分配两个数组
缺点:

  • 您需要分配两倍的内存

我刚刚偶然发现了
java.util.concurrent.Exchanger
。它充当交换点。我可能可以使用它在相邻线程之间交换单元状态列表。这比barrier要好,因为我只需要在相邻的工作进程之间进行同步。

要回答您关于双缓冲缓存问题的更新,这不是问题。CPU具有一致的高速缓存,它们知道在另一个CPU高速缓存中数据何时被改变。这不是过度同步吗?为什么需要使用int,使用布尔存储不是更合理、更高效吗?我不这么认为。在我当前的代码中,我使用的是Enum[]]。但我有一个关于共享数据可见性的问题。请参阅更新。确定。但是由于Java内存模型,可见性仍然是一个问题。正如更新中的链接所示,声明数组引用为volatile并覆盖引用将解决这一问题。尽管这也意味着,CPU不能缓存这些值(或者在修改易失性引用后丢弃这些值)。很好地读取该链接,您只需要数组引用是易失性的,而不需要数组的数据。您可以只使用一个FrontBuffer和一个BackBuffer引用,并在每次迭代中交换它们。通过这种方式,缓冲区的内容仍然可以通过cpu缓存,这些项不会易变,因此可以很好地利用cpu regi