背景
在韩顺平的Java课程中,有一个坦克大战练习项目,其中有这样一个功能需求:敌人坦克自动发射多个子弹,检测子弹是否击中我方坦克。视频中使用的是Vector
存储这个子弹队列。
代码实现
对于这一部分,我的实现代码是:
// MyPanel.java 的 run()方法
while (true) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// 检测hero的子弹是否打中敌人
for(Bullet bullet: hero.getBullets()) {
if (bullet != null && bullet.life) {
for (int i = 0; i < enemySize; i++) {
hitEnemy(bullet, enemies.get(i));
}
}
}
// 检测我方坦克是否被打中
for (Enemy enemy: enemies) {
if(enemy.isLife() && hero.isLife()) {
for(Bullet bullet: enemy.getBullets()){
if (bullet != null && bullet.life) {
hitEnemy(bullet, hero);
}
}
}
}
this.repaint();
}
代码报错
在运行过程中,在检测我方坦克是否被打中的代码部分抛出了这样的错误:
java.util.ConcurrentModificationException
at java.util.Vector$Itr.checkForComodification(Vector.java:1210)
at java.util.Vector$Itr.next(Vector.java:1163)
at org.example.MyPanel.run(MyPanel.java:196)
at java.lang.Thread.run(Thread.java:748)
分析
GPT的回答:
虽然
Vector
在 Java 中确实是线程安全的。它通过在所有修改操作(如add
、remove
等)上添加synchronized
来确保线程安全。然而,尽管Vector
在修改操作上是线程安全的,它并不能防止在遍历期间集合结构被修改引发的ConcurrentModificationException
。这是因为
Vector
的遍历操作本身并不是同步的。Vector
的迭代器(Iterator
)在设计上不具备对集合结构的并发修改进行保护,仍然会在检测到集合在迭代期间被修改时抛出ConcurrentModificationException
。这和集合的同步性是两个独立的概念。
由于该项目实现了多线程,有可能对单个子弹队列删除的同时进行遍历,使之出现报错。
解决方法
// ........
// 检测我方坦克是否被打中
//for (Enemy enemy: enemies) {
// if(enemy.isLife() && hero.isLife()) {
// for(Bullet bullet: enemy.getBullets()){
// if (bullet != null && bullet.life) {
// hitEnemy(bullet, hero);
// }
// }
// }
//}
// synchronized加锁
// 如果还涉及其他add、remove等操作 也可以使用Collections.synchronizedList()
synchronized(enemy.getBullets()) {
for (Bullet bullet : enemy.getBullets()) {
if (bullet != null && bullet.life) {
hitEnemy(bullet, hero);
}
}
}
但是,修改成以下的方法也不会报错:
for (int i=0; i < enemy.getBullets().size();i++) {
Bullet bullet = enemy.getBullets().get(i);
if (bullet != null && bullet.life) {
hitEnemy(bullet, hero);
}
}
分析:
标签:enemy,遍历,java,bullet,getBullets,安全,Vector,线程 From: https://www.cnblogs.com/walkallday/p/18458889
索引遍历 (
for (int i=0; i < enemy.getBullets().size(); i++)
):这种遍历方式直接根据索引访问
List
中的元素,并不依赖Iterator
。当使用这种方式时,List
只会获取特定索引位置的元素,不会跟踪列表是否在遍历过程中发生了修改。迭代器遍历 (
for (Bullet bullet : enemy.getBullets())
):这种方式使用了Iterator
,Iterator
是设计用于在遍历过程中检测列表结构是否被修改的。Iterator
内部维护着一个修改计数器(modCount),如果在遍历期间列表的结构发生变化(如添加、删除元素),modCount
和Iterator
的期望值不一致时,会抛出ConcurrentModificationException
。简单来说,索引遍历没有
Iterator
那种内置的“并发修改检测机制”,所以不会抛出异常。