简体   繁体   English

Swing 中两个线程上的同步块不起作用

[英]Synchronized block on two threads in Swing not working

Let me describe to you the context of my problem before I outline the issue.在我概述问题之前,让我向您描述我的问题的背景。 I'm currently in the middle of writing a game engine level editor and I'm working on the class that is going to act as the screen that the user interacts with in order to build their levels.我目前正在编写一个游戏引擎关卡编辑器,我正在研究将作为用户与之交互以构建他们的关卡的屏幕的类。 I want to make it so the screen is proportional to the size of the editor.我想让屏幕与编辑器的大小成正比。

The issue in question occurs when I begin resizing my screen and drawing on it at the same time.当我开始调整屏幕大小并同时在屏幕上绘图时,就会出现有问题的问题。 I draw from one thread and at the same time I'm editing the size of the raw pixel array that I'm drawing onto, from another thread (the EDT).我从一个线程绘制,同时我正在编辑我正在绘制的原始像素阵列的大小,从另一个线程(EDT)。 I know this is a big no-no so naturally, with no safety in place, I get the occasional IndexOutOfBounds Exception on a resize.我知道这是一个很大的禁忌,没有安全措施,我在调整大小时偶尔会遇到 IndexOutOfBounds 异常。

My thought was, I could add a synchronize block on both the resizing code and the drawing code.我的想法是,我可以在调整大小代码和绘图代码上添加一个同步块。 This way, there would be no conflict and the issue should be avoided.这样,就不会有冲突,应该避免这个问题。 However, the synchronization is being ignored completely.但是,同步被完全忽略了。 I'm still getting the same error and I'm really quite confused on why it isn't working.我仍然遇到同样的错误,我真的很困惑为什么它不起作用。 Below are the two methods of interest:以下是两种感兴趣的方法:

public void setPixel(int r, int g, int b, int x, int y) {
    synchronized (pixels){
        System.out.println("Start Draw...");
        int color = (r << 16) | (g << 8) | b;
        pixels[y * screenWidth + x] = color;
        System.out.println("End Draw...");
    }
}


@Override
public void componentResized(ComponentEvent e) {
        synchronized (pixels) {
            System.out.println("Start resize");
            int width = e.getComponent().getWidth();
            int height = e.getComponent().getHeight();
            float aspectRatio = 4 / 3f;
            if (width > height) {
                width = (int) (height * aspectRatio);
            } else if (height > width) {
                height = width;
            }
            if (width < 0 || height < 0) {
                width = 1;
                height = 1;
            }
            this.screenWidth = width;
            this.screenHeight = height;
            image = new BufferedImage(screenWidth, screenHeight, BufferedImage.TYPE_INT_RGB);
            pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
            System.out.println("End Resize");
        }
    }

I don't know if it matters (it shouldn't right?) but my screen class extends a AWT Canvas.我不知道这是否重要(应该不对?)但我的屏幕类扩展了 AWT 画布。 Also it is a listener on its parent component, so when that gets resized, it fires an event that triggers componentResized to be called.它也是其父组件上的侦听器,因此当它被调整大小时,它会触发一个触发 componentResized 的事件被调用。 Anyway, thank you, any help is appreciated.无论如何,谢谢你,任何帮助表示赞赏。

EDIT: My drawing code can be found below.编辑:我的绘图代码可以在下面找到。

new Thread(new Runnable(){
    @Override
    public void run() {
        while(true) {
            for (int y = 0; y < screen.getHeight(); y++) {
                for(int x = 0; x < screen.getWidth(); x++){
                    int r = (int) (Math.random() * 255);
                    int g = (int) (Math.random() * 255);
                    int b = (int) (Math.random() * 255);
                    screen.setPixel(r, g, b, x, y);
                }
            }
        }
    }
}).start();

There is something else I can think of.我还能想到别的。 Not only what is in the comments of the question.不仅是问题的评论中的内容。

Let me modify first the setPixel method:让我先修改setPixel方法:

public void setPixel(int r, int g, int b, int x, int y) {
    System.out.println("Called with: " + x + ", " + y); //Just added a print statement.
    synchronized (mySyncGuard){
        System.out.println("Start Draw...");
        int color = (r << 16) | (g << 8) | b;
        pixels[y * screenWidth + x] = color;
        System.out.println("End Draw...");
    }
}

Where mySyncGuard is a final property used as the synchronization guard to both setPixel and componentResized .其中mySyncGuard是用作setPixelcomponentResized的同步保护的最终属性。

And now imagine the following scenario:现在想象以下场景:

There is a loop which calls the setPixel method: this loop calls the method starting from x = 0 and y = 0 up to x < screenWidth and y < screenHeight !有一个循环调用setPixel方法:这个循环调用从x = 0y = 0开始直到x < screenWidthy < screenHeight Like the following one:像下面一个:

for (int x = 0; x < screenWidth; ++x)
    for (int y = 0; y < screenHeight; ++y) {
        int r = calculateNextR(),
            g = calculateNextG(),
            b = calculateNextB();
        setPixel(r, g, b, x, y);
    }

Where calculateNextR(), calculateNextG() and calculateNextB() are methods that produce the next red, green and blue color components respectively.其中,calculateNextR()、calculateNextG() 和 calculateNextB() 是分别生成下一个红色、绿色和蓝色分量的方法。

Now, for example, let screenWidth be 200 and screenHeight be also 200 and at some point resized to 100x100.现在,例如,让screenWidth为 200, screenHeight也为 200,并在某些时候调整为 100x100。

Now the problem is that x and y would be lets say 150 and 150 respectively while the component was about to be resized to 100, 100.现在的问题是,当组件即将调整为 100、100 时,x 和 y 分别为 150 和 150。

Our custom setPixel method is now called with x==150 and y==150.我们的自定义setPixel方法现在使用 x==150 和 y==150 调用。 But before the printing statement we added which shows the x and the y values, the componentResized manages to be called and take the lock of the synchronization guard property mySyncGuard !但是在我们添加的显示 x 和 y 值的打印语句之前, componentResized设法被调用并获取同步保护属性mySyncGuard的锁! So componentResized is now doing its job changing the size of the image to 100x100, which, in turn, changes the pixels array to something smaller than the previous data array.所以componentResized现在正在做它的工作,将图像的大小更改为 100x100,这反过来又将像素数组更改为比以前的数据数组更小的东西。

In parallel, setPixel now prints "Called with 150, 150" and waits at the synchronized block to obtain the lock of mySyncGuard , because componentResized has already currently obtained it and changing the image to 100x100 and the pixels array accordingly.同时, setPixel现在打印“Called with 150, 150”并在同步块等待获取mySyncGuard的锁,因为componentResized当前已经获取它并将图像更改为 100x100 和相应的像素数组。

So now the pixels array is smaller, the componentResized finishes resizing and finally setPixel can obtain the lock of mySyncGuard.所以现在像素数组变小了, componentResized完成了resize,最后setPixel就可以拿到mySyncGuard的锁了。

And now is the problem: the array pixels is traversed at location 150,150 while it actually has size 100x100.现在问题来了:数组像素在位置 150,150 处遍历,而实际上它的大小为 100x100。 So there you go!所以你去! IndexOutOfBoundsException... IndexOutOfBoundsException...

Conclusion :结论

  1. We need more of your code to determine what's wrong.我们需要更多您的代码来确定问题所在。
  2. The variables changing (such as screenWidth , screenHeight , image and pixels ) need to be synchronized everywhere, not only inside the setPixel and componentResized methods.变化的变量(如screenWidthscreenHeightimagepixel )需要在任何地方同步,而不仅仅是在setPixelcomponentResized方法内部。 For example, if your case is like the scenario I just described, then unfortunately the for loops should also be in a synchronized (mySyncGuard) block.例如,如果您的情况与我刚刚描述的场景类似,那么不幸的是 for 循环也应该在synchronized (mySyncGuard)块中。
  3. This wouldn't fit in a comment.这不适合评论。 If you post some more code, then we can probably tell you what went wrong (and I may delete this answer if it is not needed).如果您发布更多代码,那么我们可能会告诉您出了什么问题(如果不需要,我可能会删除此答案)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM