《Core Java 2》第七版 一书下册多线程一章,P7例1-2的第113-135行,即BallPanel里,有一个成员变量ArrayList<Ball> balls,有一个方法可以改变balls,一个方法需要遍历balls。如果同一时间里一个线程生成调用balls.add(),而此时正在遍历balls,会怎么样?我试了书上的那个例题,怎么试都不会出现同步问题。而我修改例题1-1,把BounceFrame.addBall方法变成: public void addBall() {
Runnable r = new Runnable() {
public void run() {
Ball ball = new Ball(panel);
panel.add(ball);
try {
for (int i = 0; i < STEPS; i++) {
ball.move(panel.getBounds());
panel.paint(panel.getGraphics());
Thread.sleep(DELAY);
}
} catch (InterruptedException e) {
e.printStackTrace();
} } };
new Thread(r).start();
}
在运行的时候就会抛java.util.ConcurrentModificationException,必须要用synchronize把balls给同步才行。这是为什么?为什么例题没问题,我一改就出现问题?

解决方案 »

  1.   

    此回复为自动发出,仅用于显示而已,并无任何其他特殊作用
    楼主【inkfish】截止到2008-07-25 21:35:59的历史汇总数据(不包括此帖):
    发帖的总数量:3                        发帖的总分数:600                      每贴平均分数:200                      
    回帖的总数量:239                      得分贴总数量:110                      回帖的得分率:46%                      
    结贴的总数量:3                        结贴的总分数:600                      
    无满意结贴数:0                        无满意结贴分:0                        
    未结的帖子数:0                        未结的总分数:0                        
    结贴的百分比:100.00%               结分的百分比:100.00%                  
    无满意结贴率:0.00  %               无满意结分率:0.00  %                  
    敬礼!
      

  2.   

    匆忙点了发帖,忘了增加问题点数了,保证将会追加100个点数。
    书上的例1-2源代码(Eclipse格式化过):/**
     @version 1.32 2004-07-27
     @author Cay Horstmann
     */
    import java.awt.BorderLayout;
    import java.awt.Component;
    import java.awt.Container;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.awt.geom.Ellipse2D;
    import java.awt.geom.Rectangle2D;
    import java.util.ArrayList;import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.JPanel;/**
     * Shows an animated bouncing ball.
     */
    public class BounceThread {
    public static void main(String[] args) {
    JFrame frame = new BounceFrame();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setVisible(true);
    }
    }/**
     * A ball that moves and bounces off the edges of a rectangle
     */
    class Ball {
    private static final int XSIZE = 15; private static final int YSIZE = 15; private double x = 0; private double y = 0; private double dx = 1; private double dy = 1; /**
     * Gets the shape of the ball at its current position.
     */
    public Ellipse2D getShape() {
    return new Ellipse2D.Double(x, y, XSIZE, YSIZE);
    } /**
     * Moves the ball to the next position, reversing direction if it hits one of the edges
     */
    public void move(Rectangle2D bounds) {
    x += dx;
    y += dy;
    if (x < bounds.getMinX()) {
    x = bounds.getMinX();
    dx = -dx;
    }
    if (x + XSIZE >= bounds.getMaxX()) {
    x = bounds.getMaxX() - XSIZE;
    dx = -dx;
    }
    if (y < bounds.getMinY()) {
    y = bounds.getMinY();
    dy = -dy;
    }
    if (y + YSIZE >= bounds.getMaxY()) {
    y = bounds.getMaxY() - YSIZE;
    dy = -dy;
    }
    }
    }/**
     * The panel that draws the balls.
     */
    class BallPanel extends JPanel {
    private ArrayList<Ball> balls = new ArrayList<Ball>(); /**
     * Add a ball to the panel.
     * 
     * @param b
     *            the ball to add
     */
    public void add(Ball b) {
    balls.add(b);
    } public void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2 = (Graphics2D) g;
    for (Ball b : balls) {
    g2.fill(b.getShape());
    }
    }
    }/**
     * A runnable that animates a bouncing ball.
     */
    class BallRunnable implements Runnable {
    public static final int STEPS = 1000; public static final int DELAY = 5; private Ball ball; private Component component; /**
     * Constructs the runnable.
     * 
     * @aBall the ball to bounce
     * @aPanel the component in which the ball bounces
     */
    public BallRunnable(Ball aBall, Component aComponent) {
    ball = aBall;
    component = aComponent;
    } public void run() {
    try {
    for (int i = 1; i <= STEPS; i++) {
    ball.move(component.getBounds());
    component.repaint();
    Thread.sleep(DELAY);
    }
    } catch (InterruptedException e) {
    }
    }
    }/**
     * The frame with panel and buttons.
     */
    class BounceFrame extends JFrame {
    public static final int DEFAULT_WIDTH = 450; public static final int DEFAULT_HEIGHT = 350; public static final int STEPS = 1000; public static final int DELAY = 3; private BallPanel panel; /**
     * Constructs the frame with the panel for showing the bouncing ball and Start and Close buttons
     */
    public BounceFrame() {
    setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
    setTitle("BounceThread"); panel = new BallPanel();
    add(panel, BorderLayout.CENTER);
    JPanel buttonPanel = new JPanel();
    addButton(buttonPanel, "Start", new ActionListener() {
    public void actionPerformed(ActionEvent event) {
    addBall();
    }
    }); addButton(buttonPanel, "Close", new ActionListener() {
    public void actionPerformed(ActionEvent event) {
    System.exit(0);
    }
    });
    add(buttonPanel, BorderLayout.SOUTH);
    } /**
     * Adds a bouncing ball to the canvas and starts a thread to make it bounce
     */
    public void addBall() {
    Ball b = new Ball();
    panel.add(b);
    Runnable r = new BallRunnable(b, panel);
    Thread t = new Thread(r);
    t.start();
    } /**
     * Adds a button to a container.
     * 
     * @param c
     *            the container
     * @param title
     *            the button title
     * @param listener
     *            the action listener for the button
     */
    public void addButton(Container c, String title, ActionListener listener) {
    JButton button = new JButton(title);
    c.add(button);
    button.addActionListener(listener);
    }
    }
      

  3.   

    书上的例子中:
    public void addBall() {
            Ball b = new Ball();
            panel.add(b);
            Runnable r = new BallRunnable(b, panel);
            Thread t = new Thread(r);
            t.start();……
    }
    没看出来有多个线程去调用了add(b),子线程是之后产生的,倒是你自己改的例子毫无疑问,是一点击就产生线程去执行add(b),线程推进的顺序是不可知的,你连续的点击产生的多个线程都要去调用执行add(b),肯定就会有同步问题。
      

  4.   

    类似与读写问题
    因为如你在遍历balls的同时,可能另一个线程正生成调用balls.add(),
    这就需要线程的锁机制
      

  5.   

    书上的例子是同一个线程(AWT 线程)在读取 balls 列表(取出后将每个 Ball 逐一绘制出来)和向 Balls 中添加新的 Ball,这里要注意 Start 按钮被单击后要产生 Action 事件,而事件处理代码(addBall();)是被事件处理线程(上面那个 AWT 线程)调用执行的。而你改后变成了由你自己创建的新线程负责 addBall() 了,这时就有可能出现多个线程同时对 Ball 列表操作的情况,而 for (Ball b : balls) 本质上是通过迭代器来遍历列表的,迭代器遍历要求在遍历期间不能不经过此迭代器改变列表中的元素(增加移除或替换都不行),一旦迭代期间列表元素有变,迭代器就会抛出 java.util.ConcurrentModificationException 异常。
      

  6.   

    解决方法:private ArrayList<Ball> balls = new ArrayList<Ball>();
    改成
    private ArrayList<Ball> balls = Collections.synchronizedList(new ArrayList<Ball>());for (Ball b : balls) {
        g2.fill(b.getShape());
    }
    改成
    for (int i = 0; i < balls.size(); ++i) {
        g2.fill(balls[i].getShape());  //不使用迭代器遍历列表
    }
      

  7.   

    类似与听说问题 
    因为如你在编写balls的同时,可能另一个线程正生成调用balls.add(), 
    一条或多条线相接,
    这就需要线程的锁机制。
      

  8.   

    汗,改一下:for (int i = 0; i < balls.size(); ++i) {
        g2.fill(balls.get(i).getShape());  //不使用迭代器遍历列表
    }
      

  9.   


    在书上的例子中panel是各个线程共同持有的,虽然只有主线程能够增加ball,但是每一个单独的子线程都会调用component.repaint();,在repaint()中会遍历balls,那么怎样保证add和遍历不会同时发生?
      

  10.   

    其实关键在这个地方:    public void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2 = (Graphics2D) g;
            for (Ball b : balls) {
                g2.fill(b.getShape());
            }
        }
    上面的代码只能被主线程调用还是可能被子线程调用。如果只能被主线程调用,那么调用的地方在哪里。