我网上查到的资料是因为迭代器正在遍历的时候进行了插入操作,从而抛出了java.util.ConcurrentModificationException
那么如何避免这种情况发生呢?假如我有两个线程,一个线程正在遍历这个数组,另一个线程正在将元素插入这个数组,这时悲剧就会发生,但是这种情况又无法避免,请问应该怎么做才能解决这个问题?大家可以参考下面的程序,我估计下面的程序当小球个数变多时,判断是否相碰的这个遍历就会花很长的时间,期间如果插入了一个新球必然导致异常被抛出,请教是否有解决办法?
import java.awt.*;
import java.awt.geom.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.plaf.ComponentUI;
import java.util.*;/**
 * BallPanel,一个可复用的小球碰撞面板
 * @author zjf
 * @version 1.0 2011-1-11
 */
@SuppressWarnings("serial")
public class BallPanel extends JPanel 
{
private Vector<Ball> balls = new Vector<Ball>(); //小球列表
private BallComponent component = new BallComponent(); //小球画板
private JButton btnAdd = new JButton("Add"); //Add按钮
private JButton btnStopAndContinue = new JButton("Stop"); //Stop按钮
private JButton btnClear = new JButton("Clear"); //Clear按钮
private JComboBox colorCombo = new JComboBox(); //颜色选择框
private JComboBox speedCombo = new JComboBox(); //速度选择框
private JComboBox placeCombo = new JComboBox(); //小球出现方位
private BallThread thread = new BallThread(); //小球运动线程
private int delay = 5; //小球运动的延缓时间

/**
 * 初始化小球面板
 */
public BallPanel()
{
setLayout(new BorderLayout()); //设置为BorderLayout的布局
add(component, BorderLayout.CENTER); //将小球画板加到面板中央
component.setOpaque(true); //设置画板不透明,以便能添加背景色
component.setBackground(Color.BLACK); //设置背景色

JPanel panel = new JPanel(); //创建用来放各种按钮的面板
panel.add(btnAdd); //将Add按钮放入该面板
panel.add(btnStopAndContinue); //将Stop/Continue按钮放入该面板
panel.add(btnClear); //将Clear按钮放入该面板
panel.setBackground(Color.LIGHT_GRAY);
add(panel, BorderLayout.SOUTH); //将按钮面板加到主面板南部

panel = new JPanel(new GridLayout(0, 1)); //创建用来放各种选择框的面板
panel.add(new JLabel(" Color: ")); //添加标签Color:
panel.add(colorCombo); //添加颜色选择框
panel.add(new JLabel(" Speed: ")); //添加标签Speed:
panel.add(speedCombo); //添加速度选择框
panel.add(new JLabel(" From: ")); //添加标签From:
panel.add(placeCombo); //添加方位选择框
panel.setBackground(Color.LIGHT_GRAY);
add(panel, BorderLayout.EAST); //将选择框面板加到主面板东部

//以下几句话用来向颜色选择框加入各种颜色选项
colorCombo.addItem("red");
colorCombo.addItem("orange");
colorCombo.addItem("yellow");
colorCombo.addItem("green");
colorCombo.addItem("cyan");
colorCombo.addItem("blue");
colorCombo.addItem("magenta");

//以下几句话用来向速度选择框加入各种速度选项
speedCombo.addItem("slow");
speedCombo.addItem("fast");
//速度选择框加入监听器,及时改变小球的速度
speedCombo.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent event)
{
String speed = (String)speedCombo.getSelectedItem();
if (speed.equals("slow"))
{
delay = 5;
}
else
{
delay = 1;
}
}
});

//以下几句话用来向方位选择框加入各种方位选项
placeCombo.addItem("Left-Top");
placeCombo.addItem("Left-Bottom");
placeCombo.addItem("Right-Top");
placeCombo.addItem("Right-Bottom");

//Add按钮加入监听器,当按下按钮时添加小球
btnAdd.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent event)
{
component.addBall();
}
});
//Stop/Continue按钮加入监听器,当按下按钮时暂停/继续动画
btnStopAndContinue.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent event)
{
if (btnStopAndContinue.getText().equals("Stop")) //如果当前按钮的值为Stop
{
thread.setStop(true); //将stop标志置为true
btnStopAndContinue.setText("Continue"); //将按钮的标签变为Continue
btnAdd.setEnabled(false); //Add按钮不可用
}
else
{
thread.setStop(false); //将stop标志置为false
btnStopAndContinue.setText("Stop"); //将按钮的标签变为Stop
btnAdd.setEnabled(true); //Add按钮可用
}
}
});
//Clear按钮加入监听器,当按下按钮时清空画板
btnClear.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent event)
{
balls = new Vector<Ball>(); //将球的列表清空
component.repaint(); //重画画板
}
});

thread.start(); //画画板的线程开始
}

/**
 * 主函数,主要用于测试
 * @param args
 */
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
JFrame frame = new JFrame("碰撞的小球");
frame.add(new BallPanel());
frame.setSize(400, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
});
}

/**
 * 小球运动线程
 * @author zjf
 */
private class BallThread extends Thread
{
private boolean isStop = false; //停止标记

/**
 * 线程体
 */
public void run()
{
while (true) //让它一直执行
{
if (!isStop) //当没有停止的时候
{
for (Ball ball : balls)
{
ball.move(component.getBounds()); //每个小球都移动一遍
}
component.repaint(); //重画画板
}
try {
Thread.sleep(delay); //线程延缓delay毫秒
} catch (InterruptedException e) { //捕获异常
e.printStackTrace(); //处理异常
}
}
}

/**
 * 设置stop标志
 * @param isStop 是否停止
 */
public void setStop(boolean isStop)
{
this.isStop = isStop;
}
}

/**
 * 小球的画板
 * @author zjf
 */
private class BallComponent extends JComponent
{
public BallComponent()
{
//说实话,我不是很明白这段代码是干什么的,但是要用到背景色必须用到这段代码
setUI(new ComponentUI() 
{
public void installUI(JComponent c)
{
super.installUI(c);
LookAndFeel.installColors(c, "Panel.background",
"Panel.foreground");
}
});
}
/**
 * 添加小球
 */
public void addBall()
{
double x = 0; //小球开始的x坐标
double y = 0; //小球开始的y坐标
String tmp = (String)placeCombo.getSelectedItem(); //得到方位的选择项
if (tmp.equals("Left-Top")) //如果为左上
{
x = component.getBounds().getMinX(); //x设为画板的最左边的值
y = component.getBounds().getMinY(); //y设为画板的最上边的值
}
if (tmp.equals("Left-Bottom")) //下同
{
x = component.getBounds().getMinX();
y = component.getBounds().getMaxY();
}
if (tmp.equals("Right-Top"))
{
x = component.getBounds().getMaxX();
y = component.getBounds().getMinY();
}
if (tmp.equals("Right-Bottom"))
{
x = component.getBounds().getMaxX();
y = component.getBounds().getMaxY();
}

Color color = Color.BLACK; //小球开始的颜色
tmp = (String)colorCombo.getSelectedItem(); //得到颜色的选择项
if (tmp.equals("red")) //如果为red
{
color = Color.RED; //颜色设为red
}
if (tmp.equals("orange")) //下同
{
color = Color.ORANGE;
}
if (tmp.equals("yellow"))
{
color = Color.YELLOW;
}
if (tmp.equals("green"))
{
color = Color.GREEN;
}
if (tmp.equals("cyan"))
{
color = Color.CYAN;
}
if (tmp.equals("blue"))
{
color = Color.BLUE;
}
if (tmp.equals("magenta"))
{
color = Color.MAGENTA;
}
balls.add(new Ball(x, y, color)); //在小球的列表中加入新球,球的初始方位和颜色为前面的值
}

/**
 * 绘制画板
 */
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
for (Ball ball : balls) //将小球列表中的小球都画到画板上
{
g2.setColor(ball.getColor()); //设置画布中小球的颜色
g2.fill(ball.getShape()); //画出小球的形状
}
}
}

/**
 * 小球类
 * @author zjf
 */
private class Ball
{
private static final double SIZE = 20; //小球的直径
private double x = 0; //小球所在的x坐标
private double y = 0; //小球所在的y坐标
private double vx = Math.sqrt(2) / 2; //小球在x轴的速度
private double vy = Math.sqrt(2) / 2; //小球在y轴的速度
private Color color = Color.BLACK; //小球的颜色

/**
 * 小球的构造函数
 * @param x 小球所在的x坐标
 * @param y 小球所在的y坐标
 * @param color 小球的颜色
 */
public Ball(double x, double y, Color color)
{
this.x = x;
this.y = y;
this.color = color;
}

/**
 * 小球在一个矩形边框中移动
 * @param bounds 矩形边框
 */
public void move(Rectangle2D bounds)
{
x += vx; //小球在x轴上的位移
y += vy; //小球在y轴上的位移
double minX = bounds.getMinX(); //矩形边界的最小x坐标
double minY = bounds.getMinY(); //矩形边界的最小y坐标
double maxX = bounds.getMaxX(); //矩形边界的最大x坐标
double maxY = bounds.getMaxY(); //矩形边界的最大y坐标
if (x <= minX) //如果小球越过左边界
{
x = minX; //小球的x坐标变为矩形边界的最小x坐标
vx = -vx; //小球在x轴方向的速度反向
}
if (y <= minY) //如果小球越过上边界
{
y = minY; //小球的y坐标变为矩形边界的最小y坐标
vy = -vy; //小球在y轴方向的速度反向
}
if (x + SIZE >= maxX) //如果小球越过右边界
{
x = maxX - SIZE; //小球的x坐标变为矩形边界的最大x坐标减去小球的直径
vx = -vx; //小球在x轴方向的速度反向
}
if (y + SIZE >= maxY) //如果小球越过下边界
{
y = maxY - SIZE; //小球的y坐标变为矩形边界的最大y坐标减去小球的直径
vy = -vy; //小球在y轴方向的速度反向
}
for (Ball ball : balls) //判断小球间是否发生碰撞
{
if (this.equals(ball)) //自己和自己不碰撞
continue;
if ((ball.x - x) * (ball.x - x) + (ball.y - y) * (ball.y - y) <= SIZE * SIZE) //当两球间的距离小于直径时,可认为两小球发生了碰撞
{
double degree = Math.atan((y - ball.y) / (x - ball.x)); //获取自己与发生碰撞的小球之间所形成的夹角,因为夹角只能在-pi/2-pi/2之间,所以还需判断两球的x坐标之间的关系
if (x > ball.x) //如果自己的x坐标大于发生碰撞的小球的x坐标,由数学知识可知自己应该往正向运动
{
vx = Math.cos(degree);
vy = Math.sin(degree);
}
else //如果自己的x坐标小于发生碰撞的小球的x坐标,由数学知识可知应该朝负向运动
{
vx = -Math.cos(degree);
vy = -Math.sin(degree);
}
}
}
}

/**
 * 获取小球的形状
 * @return 形状
 */
public Ellipse2D getShape()
{
return new Ellipse2D.Double(x, y, SIZE, SIZE);
}

/**
 * 获取小球的颜色
 * @return 颜色
 */
public Color getColor()
{
return color;
}

/**
 * 判断两个小球是否相同
 */
public boolean equals(Object object)
{
if (this == object) return true; //如果所指的对象相同,即两小球的确相同
if (object == null) return false; //如果要比较的小球不存在,则两小球不同
if (getClass() != object.getClass()) return false; //如果自己的类名与另一个对象的类名不同,则两小球不同
Ball ball = (Ball)object; //将另一个对象强制转化为小球
return x == ball.x && y == ball.y && color.equals(ball.color); //通过方位,颜色判断是否相同
}
}
}

解决方案 »

  1.   

    将iterator做成一个多线程单例吧,多个线程共享一个iterator,用iterator进行增减
      

  2.   

    我一开始用的是ArrayList,然后知道问题的所在后我就将ArrayList改为了Vector,我听说ArrayList不是多线程的,Vector是多线程的,所以就改了,但是貌似问题依旧存在。难道说Vector也不是线程安全的?Vector的用法应该和ArrayList的用法一样吧,直接迭代应该没有问题吧
      

  3.   

    是这样的,你用iterator的时候,在这个迭代器上创建了一个数据结构,iterator用这个数据结构来访问这个容器,这时候如果你用容器自身的add,remove方法进行增减时,这个iterator产生的数据结构不会发生变化,所以产生异常。如果用iterator迭代器本身进行增减,则相应的数据结构也会发生变化。
    避免这个异常的方法是,不要创建迭代器进行迭代的时候,不要用容器自身的方法进行增减,或者两个线程都不用迭代器,用容器自身的方法进行增减。
    这个异常和你的多线程没有关系,这只是迭代器快速失败的一个特性。
      

  4.   

    请教:如果是Map呢,那该如何遍历?
      

  5.   

    java.util.ConcurrentModificationException的问题与线程无关,只要使用了iterator在单线程中也会报这个错误,建议在遍历时规避使用iterator。