Conway生命游戏的多线程Java程序 - 边界单元的争用

时间:2010-02-24 19:50:23

标签: java multithreading synchronization conways-game-of-life java-memory-model

我正在学习java中的并发编程,并为Game of Life编写模拟。

以下是我的想法:

  • 使用int [] []存储单元格的状态
  • 将int [] []分区为t段并使用t worker threads
  • t个线程将从其段读取,计算其段中所有单元格的新值并更新单元格。
  • 一旦完成计算,他们就会等待其他工人完成障碍
  • 当屏障越过时,主线程将更新UI。
  • 工人们继续计算下一个州。

现在,各个细分市场的共同边界将会发生争执。如果一个线程在其邻居读取了之前的值之前覆盖了边界单元的状态,则该邻居的计算将是错误的。

我有什么选择?

  • 使用callable而不是runnable并让工作线程返回新值(而不是更新段本身)。穿过屏障后,主线程可以更新矩阵。此选项涉及将工作线程返回的结果复制到矩阵中。
  • 使用两个障碍。工作者线程从邻居的段中复制边界单元格并在第一道屏障处等待。一旦通过此障碍,他们就会继续计算下一个状态并更新到位。然后他们在第二道屏障等候。主线程更新UI。

我的问题是,有没有其他方法可以处理边界单元格的争用,不涉及复制数据或更高效以上两个选项?可能是使用ReaderWriterLock,volatile变量还是其他同步机制?

更新:到目前为止,double buffering solution by Peter是最干净的。但我有一个问题。 由于这两个数组是共享数据而我们没有使用任何同步(同步访问或volatile变量),它是否会产生可见性问题?多个CPU可以缓存数组值并在每次迭代时只更新数组的一部分吗?然后线程将获得边界单元格的陈旧值。这可能吗?如果没有,为什么。如果是,我该如何解决?似乎是declaring two arrays volatile will not make their individual elements volatile

5 个答案:

答案 0 :(得分:5)

我建议有2个int [] []数组。我们称它们为A和B. A将保留所有奇数编号“ticks”的值,B将保持偶数编号的滴答。

将A初始化为初始状态。然后让你的线程松散以计算每个单元格的下一个状态,并将结果放在B中的相应单元格中。 完成所有线程后,您将在B中获得新状态。现在,使用B计算每个单元格的下一个状态,并将结果存储在A中。在任何给定时间,一个数组将只读,另一个数组只写,所以永远不会有任何争用。

优点:

  • 与您现在所做的相比,不会复制数据。每个单元格只发生一次写入。
  • 不必担心边缘/角落情况,因为算法很简单。
  • 没有持续的内存分配。只需在启动时分配两个数组。

缺点:

  • 你需要分配两倍的内存。

答案 1 :(得分:1)

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

话虽如此,我有点偏颇,因为我更喜欢以功能性方式编程,除非有充分的理由不这样做。

答案 2 :(得分:1)

我会尝试以下方法:

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

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

答案 3 :(得分:0)

我偶然发现java.util.concurrent.Exchanger<V>。它充当交换点。我可以用它来交换相邻线程之间的单元状态列表。这比屏障更好,因为我只需要在相邻的工作人员之间进行同步。

答案 4 :(得分:0)

要回答有关双缓冲的缓存问题的更新,这不是问题。 CPU具有一致的缓存,并且知道何时在另一个cpu缓存中更改了数据。