今天在使用多线程完成坦克大战的java小游戏时遇到了ConcurrentModificationException异常抛出的情况,通过追随源码发现是Vector迭代过程中出现了问题
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
public boolean hasNext() {
// Racy but within spec, since modifications are checked
// within or after synchronization in next/previous
return cursor != elementCount;
}
public E next() {
synchronized (Vector.this) {
checkForComodification();
int i = cursor;
if (i >= elementCount)
throw new NoSuchElementException();
cursor = i + 1;
return elementData(lastRet = i);
}
}
public void remove() {
if (lastRet == -1)
throw new IllegalStateException();
synchronized (Vector.this) {
checkForComodification();
Vector.this.remove(lastRet);
expectedModCount = modCount;
}
cursor = lastRet;
lastRet = -1;
}
@Override
public void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
synchronized (Vector.this) {
final int size = elementCount;
int i = cursor;
if (i >= size) {
return;
}
@SuppressWarnings("unchecked")
final E[] elementData = (E[]) Vector.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
action.accept(elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
这是Vector源码中的一个内部类,这个内部类作为迭代器,当我们调用Vector的iterator方法时会返回一个该类对象
public synchronized Iterator<E> iterator() {
return new Itr();
}
通过查看报错信息了解到是在Iter这个类中的next()方法处中调用checkForComodification();
抛出的异常,也就是说我们在迭代的过程中调用next()后再调用checkForComodification()出现了问题
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
发现抛出异常的原因是modCount != expectedModCount判定结果为真,
我们跳转到modCount看看它代表的是什么,发现这个变量是Vector继承自AbstractList类中的一个属性,下面是这个属性的部分介绍:
The number of times this list has been structurally modified. Structural modifications are those that change the size of the list, or otherwise perturb it in such a fashion that iterations in progress may yield incorrect results.
那么我们便可以得知modeCount在这里代表了Vector在结构上的修改次数,也就是新增元素和删除元素这一类操作的次数,而这里的 expectedModCount就是Vector理应被修改的次数。所以就是因为实际对Vector修改的次数不等于理论上Vector被修改的次数从而造成了抛异常。通过查看源码发现,这个expectedModCount是Itr,也就是Vector的迭代器的一个类属性成员,
int expectedModCount = modCount;
默认给的初值就是modCount,并且查看源码发现expectedModCount是没有在其他任何地方出现可能被修改的操作,也就是说当我们通过Vector类对象调用iterator()方法时,它返回来的迭代器对象中的expectedModCount属性就已经固定,所以只有可能是因为在迭代的过程中modeCount发生了改变才会造成modCount != expectedModCount。
这时候我就想起来这是因为我在一个线程迭代一个Vector对象时,另一个线程在向该Vector对象中add元素,造成了modCount值的改变。我将问题代码抽象出来差不多就是下面的意思:
public class Thread08 {
public static void main(String[] args) throws InterruptedException {
Vector<Integer> integers = new Vector<>();
Test test = new Test(integers);
new Thread(test).start();
int num=0;
while(true){
num++;
integers.add(num);//向Vector对象中不断添加元素
Thread.sleep(1000);
}
}
}
class Test implements Runnable{
public Vector<Integer>x;
public Test(Vector<Integer> x) {
this.x = x;
}
@Override
public void run() {//不停地去迭代同一个Vector
while(true){
Iterator<Integer> iterator = x.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println(next);
}
}
}
}
这段程序启动后,运行一段时间就会因为上面所说的原因抛异常。
解决方式也很简单,让两个线程在要操作该Vector对象的地方指定这个Vector对象为对象锁即可
public class Thread08 {
public static void main(String[] args) throws InterruptedException {
Vector<Integer> integers = new Vector<>();
Test test = new Test(integers);
new Thread(test).start();
int num=0;
while(true){
num++;
synchronized (integers){
integers.add(num);//向Vector对象中不断添加元素
}
Thread.sleep(1000);
}
}
}
class Test implements Runnable{
public Vector<Integer>x;
public Test(Vector<Integer> x) {
this.x = x;
}
@Override
public void run() {//不停地去迭代同一个Vector
while(true){
synchronized (x){
Iterator<Integer> iterator = x.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println(next);
}
}
}
}
}