4

我目前正在编写 Fractal Explorer 程序,但遇到了一个非常奇怪的问题:我在 BufferedImage 上绘制分形,并且在该图像中随机出现黑色区域​​。截图:http: //imgur.com/a/WalM7

图像是多线程计算的:大图像被分成四个(因为我有一个四核处理器)单独计算的子图像。黑色区域出现在每个子图像的开头。它们总是矩形的,不一定按照像素的计算顺序(从左到右,但该区域并不总是延伸到子图像的远端)。

我已经验证,在绘制像素后(使用 Graphics.drawLine),BufferedImage.getRGB 立即返回像素的正确颜色,但在计算完成后,它可以返回黑色,因为像素是在屏幕上绘制的。

如果我禁用多线程计算(通过任务管理器仅将一个内核分配给 javaw.exe),问题似乎消失了,但我真的不想放弃多核计算。有没有其他人遇到过这个问题(我没有通过谷歌和stackoverflow找到任何东西),你知道如何解决吗?

Graphics.drawLine 调用在 Graphics 对象上同步;如果我另外在 BufferedImage 上同步它,则没有任何变化。

如果您想亲自查看该错误,您可以在http://code.lucaswerkmeister.de/jfractalizer/下载该程序。它也可以在 GitHub (https://github.com/lucaswerkmeister/JFractalizer) 上找到,但我最近才切换到 GitHub,并且在第一次 GitHub 提交中问题已经很明显了。

4

2 回答 2

3

我认为问题在于 BufferedImage 和 Graphics 都不是线程安全的,并且您会在计算后读取 BufferedImage 的线程中看到陈旧的值。

像你说的那样在 BufferedImage 上同步实际上应该有帮助。但请注意,您必须同步来自所有线程的所有访问,包括只读访问。所以我的猜测是,在某些组件(应该是 AWT 线程)上绘制 BufferedImage 的线程在没有同步的情况下这样做,因此会看到陈旧的值。

但是,我建议不要在多个线程之间共享一个 BufferedImage,而是给每个线程一个可以绘制的单独图像。然后,在所有线程完成后,将它们的工作组合到 AWT 线程中的新图像上。

另外,如果您还没有这样做,我建议您使用ExecutorService 。它的优点是可调用任务(在您的情况下是工作线程的图像部分)的返回值的可见性问题由库类处理。

如果将这两种方法结合起来,就不需要进行任何手动同步,这总是一件好事(因为很容易出错)。

于 2012-10-03T12:47:08.883 回答
0

缓冲图像可能不是线程安全的,因为它们的数据可能存在于显卡上。但是,这可以被覆盖。通过使用((DataBufferInt) image.getRaster().getDataBuffer()).getData()高速全图绘制的秘密技术(数据缓冲区类型取决于您选择的图像类型),您将获得未加速的图像。只要您从不两次写入同一个像素,这在理论上应该是完全安全的。但请记住在从图像中读取像素之前以某种方式加入()您的线程。实际上并不推荐来自线程的 join() 方法,因为这需要线程死亡。

相关说明:您看到的闪烁可能是 awt 呈现到屏幕的方式的产物。它以即时模式运行,这意味着您执行的每个绘图操作都会立即更新屏幕。这会减慢将多个对象直接渲染到窗口的速度。您可以通过实施双缓冲策略来解决闪烁问题。我喜欢绘制一个中间图像,然后只将该图像绘制到屏幕上。

于 2012-12-14T22:21:55.760 回答