我目前正在编写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提交中,问题已经很明显了。
答案 0 :(得分:3)
我认为问题在于,BufferedImage和Graphics都不是线程安全的,并且您在线程中看到了在计算后读取BufferedImage的过时值。
像你说的那样在BufferedImage上同步实际上应该有所帮助。但请注意,您必须同步所有线程的所有访问,包括只读访问。所以我的猜测是在一些组件(应该是AWT线程)上绘制BufferedImage的线程在没有同步的情况下这样做,因此看到过时的值。
但是,我建议不要在多个线程之间共享BufferedImage,而是为每个线程提供一个可以绘制的单独图像。然后,在完成所有线程之后,将它们的工作结合在AWT线程中的新图像上。
此外,如果您不这样做,我建议您使用ExecutorService。它的优点是,Callable任务的返回值(在您的情况下是工作线程的图像部分)的可见性问题由库类处理。
如果将这两种方法结合起来,就不需要进行任何手动同步,这总是一件好事(因为它很容易出错)。
答案 1 :(得分:0)
缓冲图像可能不是线程安全的,因为它们的数据可能存在于图形卡上。但是,这可以被覆盖。通过使用((DataBufferInt) image.getRaster().getDataBuffer()).getData()
秘密技术进行高速全图像绘制(数据缓冲区类型取决于您选择的图像类型),您将获得未加速的图像。只要你从不写两次相同的像素,理论上这应该是完全安全的。但是请记住在读取图像中的像素之前以某种方式加入()你的线程。来自线程的join()方法实际上并没有被推荐,因为这需要线程死亡。
相关说明: 您正在目击的闪烁可能是awt渲染到屏幕的方式的神器。它以立即模式运行,这意味着您采取的每个绘制操作都会立即更新屏幕。这会减慢多个对象直接渲染到窗口的速度。您可以通过实施双缓冲策略来绕过闪烁。我喜欢绘制中间图像,然后只将该图像绘制到屏幕上。