Vector线程安全问题

礼包大厅

背景

在韩顺平的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);

}

}

分析:

索引遍历 (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 那种内置的“并发修改检测机制”,所以不会抛出异常。

Copyright © 2088 今日游戏江湖 - 全品类网游活动聚合 All Rights Reserved.
友情链接