我写了一个程序,在一个窗口中显示正在移动的球,并在接触时以一定的概率相互吸收。
当前版本有效,每次调用paintComponent方法(隐含地)时都会计算球的移动:
public class ColliderPanel extends JPanel {
...
@Override
public void paintComponent(Graphics g){
super.paintComponent(g);
// calculate balls's movement
Random r = new Random();
ListIterator<Ball> it = cp.getColliderPanel().balls.listIterator();
Vector<Ball> ballsToRemove = new Vector<Ball>();
while (it.hasNext()) {
Ball b = it.next();
b.move(1.0 / 60.0);
b.collideFrame(cp.getColliderPanel().getSize());
ListIterator<Ball> it2 = cp.getColliderPanel().balls.listIterator(it.nextIndex());
while (it2.hasNext()) {
Ball b2 = it2.next();
if (b.collide(b2)) {
if (r.nextDouble() < 0.5) {
if (b.m > b2.m) {
b.swallow(b2);
ballsToRemove.add(b2);
} else {
b2.swallow(b);
ballsToRemove.add(b);
}
}
}
}
}
cp.getColliderPanel().balls.removeAll(ballsToRemove);
try {
Thread.sleep((long) (1000.0 / 60.0));
} catch (InterruptedException e) {
e.printStackTrace();
}
for(Ball b : balls) b.draw(g);
repaint();
}
...
}
现在我想将球的运动计算外包给第二个线程。我尝试创建另一个类SimulateBallsMovement implements Runnable
,它在覆盖run
方法中进行计算,并在ColliderPanel
中创建了一个新的线程,其中SimulateBallsMovement
为Runnable-object。
public class ColliderPanel extends JPanel {
private Thread simThread = new Thread(new SimulateBallsMovement());
@Override
public void paintComponent(Graphics g){
super.paintComponent(g);
// calculate balls's movement
// what to to here? how to synchronize the painting and the calculation?
for(Ball b : balls) b.draw(g);
repaint();
}
...
}
我的问题是我不知道如何同步球的绘画和运动计算? ColliderPanel
甚至需要Thread作为成员吗?我刚刚找到了关于如何同步调用相同方法的两个线程的教程,但是我想在这里做什么?
答案 0 :(得分:2)
使用Swing要记住的主要事情是,除了Swing Event Dispatch Thread(EDT)之外,几乎不应该从任何其他线程调用任何Swing方法。
EDT是一个循环,等待按键,鼠标点击和其他事件,并在每次事件发生时调用您的处理程序。
每当你的其他任何线程想要做一些会影响GUI的事情时,它应该调用SwingUtilities.invokeLater(r)
方法,其中r
是Runnable
个对象。 invokeLater(r)方法将包含r
的事件发布到事件队列,EDT将通过调用r.run()
来处理事件。然后r.run()方法可以安全地调用你需要它调用的任何Swing方法。
答案 1 :(得分:1)
这看起来像经典的制片人消费者情景。计算球运动的线程是生产者,绘制它们的线程是消费者。查看以下主题的教程:http://www.tutorialspoint.com/javaexamples/thread_procon.htm或https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html
答案 2 :(得分:1)
一个建议是将计算移动到swingworkers后台处理方法中,并在工人的完成方法中调用重绘。
答案 3 :(得分:0)
为了简化 AWT 线程机制的使用,以及在加载数据时异步组件数据获取和组件启用/禁用,我很久以前编写了 ComponentUpdateThread。它使更新数据和在正确的线程上下文中完成正确的事情变得非常容易。
new ComponentUpdateThread( button1, button2 ) {
}.start();
是您如何使用它的基本前提。 start() 的操作将递归地遍历和禁用所有侦听的组件。您可以实施三种方法。
第一个,setup(),由一个 AWT 事件线程调用,它应该对组件做任何事情(除了禁用 cons 参数会发生的事情)。这可能很简单,比如清空列表模型等。
construct() 方法由异步随机线程调用。此方法应该“获取数据”以用于填充控件,并将其放入适当的容器结构中,以便返回。
最后,在construct()返回后,一个AWT事件线程调用finished(),它应该调用getValue()来获取construct()活动的返回值,然后将数据推入适当的模型/组件中. Finished() 需要在适当的时候调用 super.finished() 来“启用”传递到 cons 中的组件。然后,您可以有条件地禁用某些内容,例如列表中的最后一个选择、复选框中的选项等,然后返回。
以下是从 javadoc 中获取的这些方法的示例。这显示了旧 API 的使用,但不包括这样一个事实,即使用泛型,您现在可以使用返回相同类型的 getValue() 使construct() 成为泛型方法。我有这个代码的一个版本,它可以完成最近添加到 Java 中的各种事情。
这段代码只是为了演示将线程使用分离成不同方法的概念,这样您就不必到处直接使用 SwingWorker,而可以使用更通用的机制,例如这样。
此代码的最新版本包括链接在一起和下一次调用的能力,以便可以进行更复杂的数据检索和填充。
最终,提供一个 ModelUpdater 类真的很好,您可以提供组件和任何相关的模型详细信息,以便对来自远程访问机制的数据源进行隔离使用。
public void setup() {
super.setup();
list.setEnabled(false);
list.clearSelection();
}
public Object construct() {
try {
Vector v = remote.getData();
Collections.sort( v );
return v;
} catch( Exception ex ) {
reportException(ex);
}
return null;
}
public void finished() {
try {
Vector v = (Vector)getValue();
list.setListData(v);
} finally {
super.finished();
list.setEnabled(true);
edit.setEnabled(false);
del.setEnaled(false);
}
}