【问题标题】:Using threads to paint panel in java在java中使用线程绘制面板
【发布时间】:2013-12-01 03:23:48
【问题描述】:

我正在编写一个具有多种不同视图的程序。其中之一是相当密集的图形(它显示一个互连的图形)。其他人只显示小而复杂的图表。

我发现主视图的绘制时间很长(即使只是绘制当前可见区域),并且在绘制时,界面的其余部分变得非常慢。

我的问题是,我可以创建一个新线程来处理绘画 - 如果是这样,它会导致性能提升,我怀疑它不会。我尝试了以下方法:

创建一个抽象类 ThreadPaintablePanel,我的复杂视图继承自。

public abstract class ThreadPaintablePanel extends JPanel{
 private Thread painter;
 public abstract void paintThread(Graphics g);

 protected void callPaintThread(Graphics g){
   if(painter != null){
     painter.interrupt();
   }
   painter = new Thread(new PaintRunnable(this, g));
   painter.start();
 }
} 

那么在我复杂的视图中,我的paintComponent 方法很简单:super.callPaintThread(g);

重写的paintThread 方法包含我所有的绘画代码。然而,这会导致未上漆的面板。我错过了什么明显的东西吗?

谢谢

【问题讨论】:

  • 应在 EDT 上修改 Swing GUI。为了尽快获得更好的帮助,请发帖SSCCE

标签: java multithreading swing paint


【解决方案1】:

除了Event Dispatch Thread (EDT) 之外,您不能让任何线程接触 GUI。让其他线程弄乱 GUI 会导致麻烦和异常。您可以使用多线程multi-buffering 技术。它涉及两个步骤:

  1. 为了并行化复杂的绘图例程,您可以简单地将整个“视图”划分为块,让一个线程将一个块绘制成一个图像。 Here is a tutorial on working with images in Java

  2. 获得图像后,您可以覆盖 paintComponent 并使用 Graphics.drawImage method 让 EDT 显示完整或部分视图,方法是将相应补丁的图像拼接在一起。

为了避免不必要的工作,请确保首先执行第一步,然后仅在视图更改后执行,否则只需再次绘制先前计算的结果。此外,如果您可以缩小视图内在帧之间发生变化的区域,请尝试仅更新部分补丁。

让我们假设您的视图至少与您的最佳线程数一样高,因此我们可以垂直划分视图。此外,让我们假设绘制任何像素所花费的工作量与其他任何像素大致相同,因此我们可以为每个补丁使用相同的大小。这两个假设使事情变得容易得多。

代码如下。如果您不需要您的计算机做任何其他事情,您可以set nThreads to your number of cores。请注意,代码还使用伪代码来表示“并行”,这在here 中进行了解释:

// let multiple threads write all patches
public BufferedImage[] writePatches(...)
{
    // Given data:
    const int nThreads = ...;         // the amount of threads that you want
    Rectangle viewBox = ...;        // The view rectangle

    // Immediate data:
    Dimension viewSize = viewBox.getSize();
    int defaultPatchHeight = (int)ceil((float)viewSize.height / nThreads);
    int lastPatchHeight = viewSize.height - (nThreads-1) * defaultPatchHeight;

    // The actual "buffer" is a set of images
    BufferedImage[] images = new BufferedImage[nThreads];

    // ...

    // pseudocode for parallel processing of a for loop
    parallel-for (nThreads, threadId)
    {
        // the starting point and size of this thread's patch
        // handle boundary (last) patch
        int w = viewBox.width;
        int h = threadId == nThread-1 ? lastPatchHeight : defaultPatchHeight;                
        int x = viewBox.x;
        int y = viewBox.y + threadId * defaultPatchHeight;

        BufferedImage patch = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = off_Image.createGraphics();

        // use g to draw to patch image here
        // better yet: Re-use existing patches and only update the parts that changed.

        images[threadId] = patch;
    }
    return images;
}

// ...

// override paintComponent
@Override
void paintComponent(Graphics gg)
{
    Graphics2D g = (Graphics2D) gg;
    // ...

    // stitch all images together (you can also just display only some images here)
    for (int threadId = 0; threadId < nThreads; ++threadId)
    {
        int w = viewBox.width;
        int h = threadId == nThread-1 ? lastPatchHeight : defaultPatchHeight;                
        int x = viewBox.x;
        int y = viewBox.y + threadId * defaultPatchHeight;

        // use pre-computed images here
        BufferedImage patch = images[threadId];
        g.drawImage(patch, x, y, ...);
    }
}

【讨论】:

  • 很好的答案 - 我会在测试后立即将其标记为正确。绘制多个图像然后渲染为一个图像是否会提高性能?我问的原因是我对线程的(有限)理解是,它们仅在需要 IO(或其他一些阻塞操作)时才能提高性能。为什么渲染图像(本质上是内存操作)会满足这个要求?
  • 您的问题远远超出了本主题的范围。你的程序现在是compute-bound (or CPU-bound)。最基本的总结:运行多个线程可以让你在单个核心上运行速度不够快的问题上投入更多的 CPU 核心。一个线程一次不能在多个内核上运行。当您拥有的线程数量使所有可用内核饱和时,您将获得最佳性能。对于通信开销很小的问题,即#threads == #cores。
  • 我明白,这就是我认为的情况。谢谢
  • awwwww - 伙计,我刚刚明白你想说什么 - 我每次滚动时都在重新生成缓冲图像,因为我每次只绘制可见区域,现在我让它在一开始就画出整个东西,每次发生变化时画一次——现在东西飞起来了,太棒了,谢谢!
  • @ZackNewsham 你能发布简短的可执行示例来说明你得到的结果吗?我知道在您上次参与此主题之后已经过去了好几年,但现在我遇到了同样的问题并试图在我的 java 应用程序中增加 fps。
【解决方案2】:

我认为您想要做的是在非 UI 线程中绘制到缓冲区(因此您可以管理一个),并在完成后复制缓冲区(在 UI 线程中)。

【讨论】:

  • 感谢您的超快速回答 - 虽然我不确定您对绘制到缓冲区的意思是什么 - 您能给我一些示例代码,或者只是一些类/方法名来查找吗?
猜你喜欢
  • 1970-01-01
  • 2012-10-19
  • 1970-01-01
  • 1970-01-01
  • 2015-04-08
  • 2013-11-20
  • 1970-01-01
  • 2014-04-02
  • 1970-01-01
相关资源
最近更新 更多