3

こんにちは、非常に単純なゲームを書いています。プレイヤーはマウスを使用して宇宙船を動かすことができ、200ms ごとに新しいビームが発射されます。このビームは while(true) ループで移動され、その y が 0 または 400 (フレームの境界) の場合、break を使用してループ (およびスレッド) を終了します。すべてのビームには独自のスレッドがあります。背景に動く星もあります。それらのすべてがビームのように動き、独自のスレッドを持っています。ご覧のとおり、多くの場合、arrayList からの追加と削除があります。すべてが機能しますが、時々次のようなエラーが発生します。

Exception in thread "AWT-EventQueue-0" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:819)
    at java.util.ArrayList$Itr.next(ArrayList.java:791)
    at spacecommander.MainPanel.paintComponent(MainPanel.java:50)

ゲームでは問題になりませんが、どうすればそれらを排除できますか? 多分私は同期か何かを使うべきですか?

編集:ここにコードがあります

public class MainPanel extends JPanel {
    private Player player = new Player(100, 100, 3, 3);
    private Point2D targetPoint = new Point2D.Float(130, 350); //Poczatkowa pozycja statku
    private ArrayList<Beam> beams = new ArrayList<Beam>();
    private InputMap imap = getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
    private ActionMap amap = getActionMap();
    private Random rand = new Random();

    public MainPanel() {
        setPreferredSize(new Dimension(300, 400));

        addMouseMotionListener(new MouseMotionHandler());

        //Rozpoczynanie watkow
        Thread t = new Thread(new PlayerMoveRunnable());
        t.start();
        Thread t2 = new Thread(new PlayerShootRunnable());
        t2.start();
    }

    public void paintComponent(Graphics g) {
        Graphics2D g2 = (Graphics2D)g;
        g2.setColor(Color.BLACK);
        g2.fillRect(0, 0, 300, 400);
        //Rysowanie gracza
        g2.drawImage(player.getImage(), (int)player.getX(), (int)player.getY(), null);
        //Rysowanie pociskow
        for (Beam beam : beams) {
            g2.drawImage(beam.getImage(), (int)beam.getX(), (int)beam.getY(), null);
        }
    }

    public void makeShortcut(String name, String keys, AbstractAction action) {
        imap.put(KeyStroke.getKeyStroke(keys), name);
        amap.put(name, action);
    }

    //Watek dziala caly czas bo gracz i tak caly czas sie rusza
    private class PlayerMoveRunnable implements Runnable {
        public void run() {
            try {
                while (true) {
                    player.moveToPoint(targetPoint);
                    repaint();
                    Thread.sleep(15);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    //Takze dziala caly czas. Dodaje nowy pocisk co 200ms
    private class PlayerShootRunnable implements Runnable {
        public void run() {
            try {
                while (true) {
                    //Wybranie pocisku do wystrzelenia w zaleznosci od mode gracza
                    Thread t;
                    switch (player.getBeamMode()) {
                    case 1:
                        t = new Thread(new BeamMoveRunnable(new Beam1(100, 100, 10, 10, 10)));
                        break;
                    }
                    t.start();
                    Thread.sleep(200);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private class BeamMoveRunnable implements Runnable {
        private Beam beam;

        public BeamMoveRunnable(Beam beam) {
            this.beam = beam;
        }

        public void run() {
            Beam beam = this.beam;
            beams.add(beam);
            try {
                while (true) {
                    if (beam.getY() <= 0) {
                        beams.remove(beam);
                        break;
                    }
                    beam.move();
                    repaint();
                    Thread.sleep(20);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private class MouseMotionHandler extends MouseAdapter {
        public void mouseMoved(MouseEvent event) {
            targetPoint = event.getPoint();
        }
    }
}
4

3 に答える 3

2

ご想像のとおり、これは同期の問題のようです。おそらく、描画コードがbeams-listを反復している間BeamMoveRunnableに、リストが同時に変更され (ビームの追加または削除)、ConcurrentModificationExceptionが発生します。個人的には、ビームを動かすのに別のスレッドを使用するのではなく、最初にゲームを更新し (一度にすべてのビームを 1 つずつ移動するなど)、次に画面を再描画する単純なループを使用します。ただし、一度に 1 つのスレッドだけがリストにアクセスできるように、リストへのアクセスを同期することもできます。

于 2013-01-05T11:59:04.447 に答える
1

これは通常、反復中にリストからアイテムを追加または削除しようとしたときに発生します。

for (String s : list) {
    list.add("abc"); //ConcurrentModificationException
}

MainPanel クラスの 50 行付近のコードを見ずに、より具体的に説明することは困難です (cf. stacktrace: at spacecommander.MainPanel.paintComponent(MainPanel.java:50))。

編集

編集後の簡単な変更は、ArrayList の代わりにスレッド セーフな CopyOnWriteArrayList を使用して、Beam オブジェクトを保持することです。

于 2013-01-05T11:31:29.960 に答える
1

複数のスレッドを使用しているため、異なるスレッドがbeams arraylist. 以下のように、同期ブロックを使用して同時変更例外を回避できます

リストのループ

synchronized (beams) {
    for (Iterator it = beams.iterator(); it.hashNext(); ) {
        Beam beam = (Beam) it.next();
       g2.drawImage(beam.getImage(), (int)beam.getX(), (int)beam.getY(), null);
    }
}

リストにアイテムを追加する

syncronized (beams) {
    beams.add(beam);
}

リストからアイテムを削除する

syncronized (beam) {
    list.remove(beam);
}
于 2013-01-05T12:00:31.937 に答える