《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给同步才行。这是为什么?为什么例题没问题,我一改就出现问题?
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给同步才行。这是为什么?为什么例题没问题,我一改就出现问题?
解决方案 »
- 高分 --java面试题
- 有关消息框的问题??
- java 内存操作问题
- java1.5容器类继承问题
- 一个Java程序的运行流程,大家帮忙分析一下思路,共享,感谢
- java面试题求解!
- 用什么方法能实现断点续传功能?TIA各位大哥了,小女子有礼了。
- 请教两个小问题!
- 我在测试我.class文件时总提示这样的错误:Exception in thread "main" java.lang.noclassdefoundError:myclass
- 谁有 JBulider 的文档资料(尤其是入门资料)?我只有这些分了
- java和javascript有什么相同和有什么不同的地方!!!
- 我们课程设计要求设计一个磁卡充值的管理程序,请问设计思路是什么?
楼主【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 %
敬礼!
书上的例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);
}
}
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),肯定就会有同步问题。
因为如你在遍历balls的同时,可能另一个线程正生成调用balls.add(),
这就需要线程的锁机制
改成
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()); //不使用迭代器遍历列表
}
因为如你在编写balls的同时,可能另一个线程正生成调用balls.add(),
一条或多条线相接,
这就需要线程的锁机制。
g2.fill(balls.get(i).getShape()); //不使用迭代器遍历列表
}
在书上的例子中panel是各个线程共同持有的,虽然只有主线程能够增加ball,但是每一个单独的子线程都会调用component.repaint();,在repaint()中会遍历balls,那么怎样保证add和遍历不会同时发生?
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
for (Ball b : balls) {
g2.fill(b.getShape());
}
}
上面的代码只能被主线程调用还是可能被子线程调用。如果只能被主线程调用,那么调用的地方在哪里。