编辑:已解决,请在下面查看我的解决方案。
首先,这是我在这里的第一个问题,所以如果我犯了任何错误,请告诉我。
我正在尝试用Java编写一个曼德布洛特分形程序,用于培训目的。我想要拥有的所有功能的理想是分形器(http://www.fractalizer.de/en/),但现在,我会对在屏幕上绘制曼德布洛特集的程序感到满意(而不是,例如,将其写入图像文件)。当然,我希望程序速度快,所以我想我可以将计算拆分为多个线程以利用我的多核处理器;例如,在四核系统上,映像将被分成 2x2=4 个映像,每个映像由单独的线程计算。所有这些线程都会传递一个 Graphics 对象,它们在计算像素时在其上绘制像素。
我第一次尝试让它工作是让线程在 BufferedImage.getGraphics() 上绘制,并让 paint() 方法不断调用 repaint(),只要图像没有完成:
g.drawImage(tempImg, 0, 0, null);
if (waiterThread.isAlive())
{
try
{
Thread.sleep(10);
} catch (InterruptedException e)
{
// do nothing
}
repaint(10);
}
(waiterThread 将所有计算线程一个接一个地连接起来,所以只要 waiterThread 还活着,至少有一个计算线程还没有完成。
这有效,但由于频繁的重新绘制,会导致画布上出现丑陋的闪烁。
然后,通过一个小的测试程序,我发现 Graphics.draw*anything* 在绘制方法返回之前立即绘制在屏幕上,所以我目前的方法如下:
- 一个具有 GridLayout 的面板,其中包含 2x2(在 <4 核系统上,1x1)MandelbrotCanvas 对象
- 每个 MandelbrotCanvas 对象将在第一次 paint() 调用时初始化一个计算线程,将自己的 Graphics 对象传递给它(实际上,我正在使用一个自定义 GroupGraphics 类,该类将一个图形调用传递给多个图形,将图像"备份"到 BufferedImage.getGraphics() 中,但这并不重要),然后启动计算线程。
- 该面板将在其paint()方法中从每个MandelbrotCanvases中获取计算线程并join()它们。
不幸的是,这只会创建一个黑屏。仅当计算完成后,才会显示图像。
将多个线程绘制到一个组件上的正确方法是什么?
编辑:
我不知道的是:只允许事件调度线程在 AWT 组件上绘制(粗略地说),这意味着上面的最后一种方法不可能奏效 - 显然,它应该抛出异常,但我没有得到一个。我的解决方案是使用第一种方法 - 将图像绘制到 BufferedImage 上并将其绘制到 Canvas 上 - 唯一的修改是我重载 update() 方法来调用 paint() 方法而不清除绘画区域:
public void update(Graphics g)
{
paint(g);
}
所以我想我对一般问题的回答("如何让多个线程绘制到 AWT 组件上?")将是:你不能,这是不允许的。让线程绘制到 BufferedImage.getGraphics() 上,并重复绘制该图像。像上面一样重载 update() 方法以避免闪烁。(现在看起来真的很棒。另一个我不能在我的情况下使用但仍然不错的提示是,有一个 repaint() 的变体,它采用矩形参数来指定必须重绘的区域,还有一个变体采用时间参数(以毫秒为单位),因此重绘不必立即发生。
编辑2:此链接提供了非常有用的信息:http://java.sun.com/products/jfc/tsc/articles/painting/index.html
只有 GUI 线程可以直接在组件上绘制。
因此,必须调用重绘方法。
当您具有后台计算时,要强制快速绘制,应使用需要时间的版本作为参数。
这里有一些细节:
注意:如果在组件上发生多次对 repaint() 的调用,则在 初始重绘请求被处理,所述多个请求可能是 折叠成对 update() 的单个调用。算法 确定何时应折叠多个请求是 依赖于实现。如果多个请求被折叠,则 生成的更新矩形将等于 折叠请求中包含的矩形。
您必须向 EDT 发送请求。
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
Rectangle r = myCurrentWorkingThread.getFinishedRectangle();
myPainter.repaint(r);
}
});
这个想法是,您不会逐个像素地重新绘制,而是为工作线程提供更大的块。一旦他们完成了一个工作单元,他们就会通知将完成实际工作的主要对象(myPainter)。此构造 (EventQueue.invokeLater) 将保证它将位于事件调度程序线程上。