Почему не все методы, которые одновременно изменяют общий объект, нуждаются в модифицированном синхронизаторе? - PullRequest
0 голосов
/ 19 мая 2019

Я хотел бы понять, когда добавить модификатор synchronized к методу, который изменяет общий объект, а когда нет.

Я написал модифицированную версию прыгающие шарики игра.Каждый шар (я назвал его «камень») - это thread.Чтобы управлять процессом repaint, я сохраняю HashSet камней, и этот набор обрабатывается несколькими методами:

  • , когда я добавляю новый камень (если пользователь нажимает кнопку огня наGUI);

  • , когда я убиваю один или несколько камней (если пользователь нажимает на камни на panel);

  • , когдаодин из камней-убийц (особый «плохой» вид камней) касается любого из нормальных «хороших» камней и убивает их;

  • и, наконец, когда метод paintComponent()звонил.

Ну, я думал, что все методы, которые обрабатывают эти вещи, должны быть объявлены synchronized.Но я предпринял некоторые попытки и обнаружил, что:

  • в некоторых случаях необходим модификатор synchronized (если я его удаляю, я получаю исключение, ОК, это то, что я ожидал);

  • В некоторых других случаях я удалил модификатор synchronized и Я никогда не получал никаких исключений , даже запуская программу все больше и больше, создавая и убивая тонны убийцкамни и / или хорошие камни.

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

Теперь я присоединяю класс StoneSet, где всеэти методы определены.Этот класс является singleton : только один его экземпляр создается и используется почти всеми другими объектами в приложении.Я очистил класс от всего ненужного кода и написал много комментариев, чтобы помочь читателям понять класс и (надеюсь) рассказать мне, что происходит.Я прошу прощения за длинное вложение (более 100 строк).

package rollingstones;

import java.awt.Graphics;
import java.util.HashSet;
import java.util.Iterator;

class StoneSet {

    private final HashSet<Stone> set = new HashSet<>(64);       // this is the set
    private AreaGrafica areaGrafica;                            // this is the JPanel

    void setAreaGrafica(AreaGrafica areaGrafica) {              // invoked at the beginning
        this.areaGrafica = areaGrafica;                         
    }

    /**
     * This method is called by the paintComponent() of the panel.
     * HERE THE SYNCHRONIZED MODIFIER IS NEEDED: IF I REMOVE IT, I GET java.util.ConcurrentModificationException
     */
    synchronized void redrawAll(Graphics g) {
        final Iterator<Stone> iter = set.iterator();
        Stone stone;
        while (iter.hasNext()) {
            stone = iter.next();
            g.setColor(stone.getColor());
            g.fillOval(stone.getX(), stone.getY(), stone.getSize(), stone.getSize());
        }
    }

    /**
     * This method is called when the user clicks the GUI's Fire button (actionPerformed awt event).
     */
    void addGoodStone() {
        Stone stone = new GoodStone();              // GoodStone is a Stone
        addStone(stone);
    }

    /**
     * This method is called when the user clicks the GUI's Killer button (actionPerformed awt event).
     */
    void addKillerStone() {
        Stone stone = new KillerStone();            // KillerStone is a Stone
        addStone(stone);
    }

    /**
     * This method adds a stone into the set, so it modifies the set, but...
     * ...HERE I REMOVED THE SYNCHRONIZED MODIFIER AND I NEVER GOT ANY EXCEPTION.
     */
    private void addStone(Stone stone) {
        stone.start();                              // start the thread (each stone is a thread)
        set.add(stone);                             // put the stone into the set
        System.out.print(set.size() + " ");
    }

    /**
     * This method is called when the user clicks a point on the panel (mouseClicked awt event).
     * This method removes more than one of the stones from the set, but...
     * ...HERE I REMOVED THE SYNCHRONIZED MODIFIER AND I NEVER GOT ANY EXCEPTION.
     */
    void killStone(int xClicked, int yClicked) {
        final Iterator<Stone> iter = set.iterator();
        Stone stone;

        while (iter.hasNext()) {
            stone = iter.next();

            if (SOME CONDITIONS, READING THE STONE STATUS) {
                stone.interrupt();                      // stop the thread
                iter.remove();                          // remove the stone from the set
                System.out.print(set.size() + " ");
            }
        }

        if (set.isEmpty()) {
            areaGrafica.repaint();                      // remove the image of the final stone from the panel
        }
    }


    /**
     * This method is called by the run() method of the killer stones (see later).
     * HERE THE SYNCHRONIZED MODIFIER IS NEEDED: IF I REMOVE IT, I GET java.util.ConcurrentModificationException
     */
    synchronized void killNeighbouringGoodStones(int x, int y, int radius) {
        final Iterator<Stone> iter = set.iterator();
        Stone stone;

        while (iter.hasNext()) {
            stone = iter.next();

            if (SOME OTHER CONDITIONS, USING THE STONE STATUS) {
                stone.interrupt();                      // stone is a thread
                iter.remove();                          // remove the stone from the set
                System.out.print(set.size() + " ");
                }
            }
        }
    }

}

/**
 * This is the run() method of the Stone class.
 */
@Override
public void run() {
    try {                                   // while into the try
        while (true) {
            animate();                      // this simple method changes the stone state (*)
            Stone.areaGrafica.repaint();
            Thread.sleep(SLEEP);            // SLEEP is 50 ms
        }
    } catch (InterruptedException ex) {
        System.err.println(ex.getMessage());
    }
}

(*) if the stone is a killer stone, the animate() method is overridden:
@Override
void animate() {
    super.animate();
    set.killNeighbouringGoodStones(getCenterX(), getCenterY(), getSize() / 2);      // here set is the singleton StoneSet
}

/**
 * This is the paintComponent() method of the panel.
 */
@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);                    
    set.redrawAll(g);
}

Я думал, что модификатор synchronized является обязательным для всех методов, которые обращаются к общемуобъект, но, видимо, это не так.

РЕДАКТИРОВАТЬ

У меня была идея: возможно ...

  • ... redrawAll () иДля killNeighbouringGoodStones () требуется модификатор synchronized, поскольку эти методы вызываются другими потоками, которые я создал, в то время как ...

  • ... addStone () и killStone () могут не синхронизироваться, посколькуметоды вызываются слушателями API с поддержкой графического интерфейса.

Может ли это быть правдой?

Ответы [ 2 ]

3 голосов
/ 20 мая 2019
  1. Существует огромная разница между: «Я не получаю исключения», «Код правильный» и «Код, развернутый в настоящее время, работает правильно».

  2. Если изменяемый объект защищен синхронизированным блоком, КАЖДЫЙ доступ к этому объекту ДОЛЖЕН быть защищен блоком, синхронизированным на ЖЕ мониторе.В противном случае код не является потокобезопасным, и могут произойти неприятности (и это касается как чтения, так и записи).

  3. Если код не является потокобезопасным, он может работать достаточно хорошо, особенно если вы пишете игру, а не банковскую систему.Действительно серьезные ошибки могут появиться только при определенных обстоятельствах: одно из миллиарда выполнений, в какой-то конкретной версии JVM, когда выполняется какое-то дополнительное условие и т. Д.

  4. Исключения, которыми вы являетесьполучение, вероятно, не типичные ошибки потоков : они происходят из-за того, что HashSet является отказоустойчивой коллекцией, и вы не можете удалять элементы из нее, используя разные итераторы одновременно.Другими словами: даже если вы синхронизируете сам HashSet, вы все равно получите эту ошибку, хотя - с точки зрения безопасности потоков - код будет правильным.

  5. Как уже прокомментировал Соломон Слоу, ваш дизайн на самом деле не подходит для использования потоков.Поэтому я предполагаю, что вы делаете это для удовольствия / для изучения потоков Java.Тогда особенно важно делать все правильно: -)

0 голосов
/ 19 мая 2019

Вы используете sychronized, если несколько потоков пытаются изменить один и тот же объект одновременно.Как вы заметили, если удалить синхронизированный из некоторых из вас методов, вы получите исключение, потому что все они пытаются изменить HashSet одновременно.

Вы должны убедиться, что следующие вещи не происходят одновременно(запускается двумя разными потоками):

1. Iteration over the values in the the HashSet
2. Modification of the Data inside the HashSet

С точки зрения стиля кода синхронизированная никогда не является «обязательной».Разработчик должен выяснить, где его использовать, чтобы ваш код работал правильно.

Вы также хотите взглянуть на ключевое слово 'volatile', чтобы убедиться, что все потоки видят один и тот же контент.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...