Синхронизированный блок на двух потоках в Swing не работает - PullRequest
0 голосов
/ 07 января 2020

Позвольте мне описать вам контекст моей проблемы, прежде чем я обрисую ее. Сейчас я нахожусь в процессе написания редактора уровней игрового движка и работаю над классом, который будет выступать в качестве экрана, с которым взаимодействует пользователь, чтобы построить свои уровни. Я хочу сделать так, чтобы экран был пропорционален размеру редактора.

Данная проблема возникает, когда я начинаю изменять размер экрана и одновременно рисую на нем. Я рисую из одного потока, и в то же время я редактирую размер массива необработанных пикселей, из которого я рисую, из другого потока (EDT). Я знаю, что это большое нет-нет, поэтому, естественно, без безопасности, я получаю случайное исключение IndexOutOfBounds при изменении размера.

Я думал, что могу добавить блок синхронизации как для кода изменения размера, так и для кода чертежа. Таким образом, конфликта не будет, и проблемы следует избегать. Однако синхронизация полностью игнорируется. Я все еще получаю ту же ошибку, и я действительно совершенно не понимаю, почему она не работает. Ниже представлены два метода, представляющие интерес:

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");
        }
    }

Я не знаю, имеет ли это значение (не так ли?), Но мой класс экрана расширяет AWT Canvas. Также он является слушателем своего родительского компонента, поэтому, когда его размер изменяется, он запускает событие, которое инициирует вызывание componentResized. В любом случае, спасибо, любая помощь приветствуется.

РЕДАКТИРОВАТЬ: мой код для рисования можно найти ниже.

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();

1 Ответ

0 голосов
/ 07 января 2020

Есть кое-что еще, о чем я могу думать. Не только то, что в комментариях к вопросу.

Позвольте мне сначала изменить метод 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...");
    }
}

Где mySyncGuard - это конечное свойство, используемое как защита синхронизации для setPixel и componentResized.

А теперь представьте следующий сценарий:

Существует al oop, который вызывает метод setPixel: это l oop вызывает метод, начиная с x = 0 и y = 0 до x < screenWidth и y < screenHeight! Подобно следующему:

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);
    }

Где Функция «executeNextR ()», метод «CalcuNextG ()» и «calcNextB ()» представляют собой методы, которые вырабатывают следующие компоненты красного, зеленого и синего цветов соответственно.

Теперь для Например, пусть screenWidth будет 200, а screenHeight будет также 200 и в какой-то момент будет изменено до 100x100.

Теперь проблема в том, что x и y будут, скажем, 150 и 150 соответственно, в то время как компонент должен был быть изменен до 100, 100.

Наш пользовательский метод setPixel теперь вызывается с x == 150 и y == 150. Но перед добавленным оператором печати, который показывает значения x и y, умудряется вызвать componentResized и получить блокировку свойства защиты синхронизации mySyncGuard ! Поэтому componentResized теперь выполняет свою работу, изменяя размер изображения на 100x100, что, в свою очередь, меняет массив пикселей на нечто меньше , чем предыдущий массив данных.

Параллельно setPixel теперь печатает "Вызывается с 150, 150" и ждет в синхронизированном блоке, чтобы получить блокировку mySyncGuard , потому что componentResized уже в настоящее время получил его и изменил изображение на 100x100 и соответственно массив пикселей .

Так что теперь массив пикселей меньше, componentResized заканчивает изменение размера и, наконец, устанавливаетPixel может получить блокировку mySyncGuard.

И теперь проблема в том, что пиксели массива пересекаются в местоположении 150,150, тогда как на самом деле имеют размер 100x100. Итак, вы go! IndexOutOfBoundsException ...

Заключение :

  1. Нам нужно больше вашего кода, чтобы определить, что не так.
  2. Переменные переменные (такие как screenWidth , screenHeight , image и пикселей ) необходимо синхронизировать везде, а не только внутри методов setPixel и componentResized , Например, если ваш случай похож на сценарий, который я только что описал, то, к сожалению, циклы for также должны быть в блоке synchronized (mySyncGuard).
  3. Это не помещается в комментарии. Если вы разместите еще какой-нибудь код, то мы, вероятно, расскажем вам, что пошло не так (и я могу удалить этот ответ, если он не нужен).
...