J'essaye de supprimer les objets qui sont en dehors de JPanel. Cependant, lorsque je fais cela, j'obtiens cette erreur
et mon programme plante. Mon conférencier m'a dit que c'était parce que deux Threads accédaient à la ArrayList qui stockait mes objets.
J'ai fait pour synchroniser les fonctions mais ce n'est pas le cas ne fonctionne pas.
Bullet
import javax.swing.*; import java.awt.*; import java.util.ArrayList; public class Bullet { //Environment public static ArrayList<Bullet> bullet = new ArrayList<>(); private String path = Application.path; private GamePanel gp; //properties private int x,y; private int width,height; private int yVector; private Image image; Bullet(GamePanel gp, int x, int y){ image = new ImageIcon(path+"\\images\\javaicon.png").getImage(); width=image.getWidth(null); height=image.getHeight(null); this.gp=gp; this.x=x; this.y=y; yVector=5; } public void move(){ if(y< -height){ bullet.remove(this); } y-=5; } public Image getImg(){ return image; } public int getX(){ return x; } public int getY(){ return y; } }
Classes pertinentes:
Application
import javax.swing.*; import java.awt.*; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.security.Key; import java.util.ArrayList; public class GamePanel extends JPanel implements Runnable, KeyListener{ private String path = Application.path; Image gameOverImg = new ImageIcon(path+"//images//gameover1.png").getImage(); private Ihsan ihsan; private ArrayList <David> david = new ArrayList<>(); private int enemies=5; private boolean pause=false; private boolean gameOver=false; GamePanel(){ ihsan = new Ihsan(this); for(int i=0; i<enemies; i++){ david.add(new David(this)); } setFocusable(true); requestFocusInWindow(); addKeyListener(this); } @Override public void run() { while (!pause){ repaint(); for(David david:david){ david.move(); } for(Bullet bullet:Bullet.bullet){ bullet.move(); } try{Thread.sleep(30);} catch (InterruptedException e){} } } public void paint(Graphics g){ Graphics2D g2d = (Graphics2D) g.create(); g2d.setColor(Color.GRAY); g2d.fillRect(0,0 ,getWidth(), getHeight()); for(David david : david){ g2d.drawImage(david.getImg(), david.getX(), david.getY(), null); } g2d.drawImage(ihsan.getImg(), ihsan.getX(), ihsan.getY(), null); for (Bullet bullet:Bullet.bullet){ g2d.drawImage(bullet.getImg(), bullet.getX(), bullet.getY(), null); } if(gameOver){ g2d.drawImage(gameOverImg,0,getHeight()/4,null); } } private static final Dimension DESIRED_SIZE = new Dimension(600,700); @Override public Dimension getPreferredSize(){ return DESIRED_SIZE; } public void setGameOver(boolean gameOver) { this.gameOver = gameOver; } @Override public void keyPressed(KeyEvent e) { int key=e.getKeyCode(); if (key==KeyEvent.VK_D || key==KeyEvent.VK_RIGHT){ ihsan.move(4,0); System.out.println("Right Key"); } if (key==KeyEvent.VK_A || key== KeyEvent.VK_LEFT){ ihsan.move(-4,0); System.out.println("Left Key"); } if(key==KeyEvent.VK_SPACE){ Bullet.bullet.add(new Bullet(this,ihsan.getX()+(ihsan.getWidth()/2), ihsan.getY())); } } @Override public void keyTyped(KeyEvent e) { } @Override public void keyReleased(KeyEvent e) { } public boolean getGameOver(){ return gameOver; } }
GamePanel
import javax.swing.*; public class Application { public static String path ="C:\\Users\\jarek\\OneDrive\\NUIG Private\\(2) Semester 2 2019\\Next Generation Technologies II CT255\\Assignment 3\\"; private Application(){ JFrame frame = new JFrame("Ihsan The Defender"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); GamePanel gamePanel= new GamePanel(); frame.add(gamePanel); frame.pack(); frame.setLocationRelativeTo(null); frame.setResizable(false); frame.setVisible(true); new Thread(gamePanel).start(); } public static void main (String args[]){ new Application(); } }
Bullet strong >
public void move(){ if(y< -height){ synchronized (this) { bullet.remove(this); } } y-=5; }
3 Réponses :
Le bloc synchronisé
doit être autour de chaque morceau de code qui a accédé ou modifie la ArrayList
. L'objet mis entre parenthèses doit être le même: c'est le verrou.
Créez un champ de type Object
nommé bulletLock
par exemple, et utilisez-le comme verrou, chaque fois que vous accédez à bullet
. p>
L'erreur se produit car vous supprimez une puce alors qu'un autre thread est dans une boucle for de la liste. Comme il y a une modification simultanée, elle ne peut pas continuer en toute sécurité.
Une autre solution serait de faire une copie de la ArrayList
avant votre boucle for.
Est-ce que faire une copie de ArrayList ne serait pas inefficace?
Les puces dans ArrayList seraient les mêmes instances, donc cela fonctionnerait, mais utiliserait plus de mémoire. Le mieux serait d'utiliser un bloc synchronisé.
J'ai essayé de le synchroniser et il plante toujours. Je pense que je fais quelque chose de mal. ici
Vous avez oublié de synchroniser l'ajout dans keyPressed
Votre problème actuel ne concerne pas la synchronisation, mais que vous modifiez la liste à puces en l'itérant:
// Bullet.java#move(): public boolean move(){ if(y< -height){ return true; // instruct GamePanel.run() to remove this bullet } y-=5; return false; // keep this bullet } // GamePanel.java#run(): Iterator<Bullet> it = Bullet.bullet.iterator(); while (it.hasNext()) { Bullet bullet = it.next(); if (bullet.move()) { // if bullet should be removed it.remove(); // remove it from the list } }
La synchronisation n'aidera pas, car les deux actions se produisent dans le même thread.
/ p>
Pour résoudre ce problème, la méthode Bullet.move ()
doit renvoyer un booléen indiquant s'il doit être supprimé de la liste. Et GamePanel.run ()
ne doit pas utiliser une boucle for améliorée mais un itérateur (supprimer un élément d'une liste à l'aide de Iterator.remove ()
est sûr si c'est le seul active Iterator
):
// GamePanel.java#run(): for (Bullet bullet:Bullet.bullet) { //your code is iterating over Bullet.bullet here bullet.move(); //you call Bullet#move here } // Bullet.java#move(): public void move(){ if(y< -height){ bullet.remove(this); //this will remove the current bullet from Bullet.bullet // ultimately causing the ConcurrrentModificationException in GamePanel.run() } y-=5; }
Il y a aussi d'autres problèmes:
#repaint ()
à partir de votre propre thread au lieu de Swing EDT Bullet.bullet
sans synchronisation (ce qui peut conduire à une ConcurrentModificationException
dans GamePanel.paint ()
)
Eh bien oui, je suis conscient du deuxième problème. C'est dans la question. Je cherche une solution. Je pensais que la synchronisation réglerait le problème.
@ ertagon2 J'ai mis à jour ma réponse avec une solution possible
Merci beaucoup
Une solution simple au problème décrit dans la réponse de Thomas Kläger pourrait être:
Iterator<Bullet> it = Bullet.bullet.iterator(); while (it.hasNext()) { Bullet bullet = it.next(); bullet.move(); }