关于多线程:使用线程在java中绘制面板

Using threads to paint panel in java

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
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)触摸GUI。让其他线程弄乱GUI会导致麻烦和异常。您可以使用多线程多缓冲技术。它涉及两个步骤:

  • 为了并行化复杂的绘图例程,您可以简单地将整个"视图"划分为补丁,让一个线程将一个补丁绘制到一个图像中。这是一个使用Java处理图像的教程。

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

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

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

    代码如下。如果您不需要计算机执行任何其他操作,则可以将nThreads设置为核心数。请注意,代码也使用伪代码"parallel for",这里解释:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    // 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, ...);
        }
    }


    我想你想要做的是绘制到非UI线程中的缓冲区(所以你管理的那个),并在完成时(在UI线程中)复制缓冲区。