Java,无限循环的替代方案是什么?

7
我正在制作一个通过数组显示细胞生长的程序。我已经实现了当我按下“开始”按钮时,数组每10秒更新一次,在while(true){}循环中。问题在于,我想能够通过按下“暂停”按钮来停止循环,但在循环中,它不会让我使用任何控件。我需要除了无限循环以外的其他东西来刷新帧。
我有点新手,但我目前正在上Java课程,所以我对这种语言有一些了解。

1
开始和暂停按钮是什么意思?你的程序在某个GUI中运行吗?这是为你的课程设置的环境吗? - Uri
9个回答

10
我建议使用一个单独的线程来处理数组。确保你正在使用线程安全的对象(查看 Java 文档),并且在需要时只需在您的线程对象上调用 .start()。保留对它的指针,以便可以通过 setPaused(true) 暂停它。
类似这样...
class MyArrayUpdater extends Thread {
    private volatile boolean enabled = true;
    private volatile boolean paused = true;

    @Override
    public void run() {
        super.run();
        try {
            while (enabled) {
                if (!paused) {
                    // Do stuff to your array here.....
                }
                Thread.sleep(1);
            }
        } catch (InterruptedException ex) {
            // handle ex
        }
    }

    public void setEnabled(boolean arg) {
        enabled = arg;
    }

    public void setPaused(boolean arg) {
        paused = arg;
    }
}

1
由于setPaused和setEnabled应该从其他线程调用,因此您应该使enabled和paused布尔值成为volatile,或在读取/设置它们时进行同步。 - Paul Wagland
1
如果应用程序暂停了很长时间(例如1分钟),那么这将消耗大量资源,因为while循环将以全速运行,只是为了将线程设置为休眠一毫秒并重新启动它。这将创建大约60,000个不必要的调用,仅用于1分钟的停止。 - OscarRyz
实际上,这是一个非常糟糕的解决方案。当 paused == true 时,没有工作需要完成,这段代码会每1毫秒产生两次上下文切换(即:非常昂贵),假设操作系统能够提供如此精确并且能够跟上速度。净效果将会减慢其他正在执行实际工作的线程。更好的解决方案是等待条件 "paused == false" 成立。可以使用传统的锁和 Object#wait、#notify,或者使用 java.util.concurrent.locks.Lock/Condition 和适当的方法。奇怪的是,这个解决方案得到了最多的投票! - Dimitris Andreou
Paul Clapham的回答更好。如果应用程序使用SWT而不是swing,我会感到惊讶,它没有类似的计时器事件。 - Geoff Reedy
等待时间或锁定可以有许多不同的处理方式。sleep(1) 可以被配置,或者像 Dimitris 评论中提到的那样可以锁定直到 #notify(我喜欢这种方法)。总的来说,上面的方法是处理线程过程的一种简单和通用的方式。我也不会反对下面基于计时器的解决方案是一个有效的解决方案,只要提问者正在使用 Swing 并且每秒更新足够快即可。 - Chris Kannon

10
你需要使用一个 计时器,它会改变组件的状态(在这种情况下是细胞生长),然后调用 JComponent.repaint()
这个计时器可以被 取消 以暂停它,然后重新开始,只需创建一个新的:
因此,您可以定义以下两个方法:
private Timer timer;
...
public void startPaiting() {
    timer = new Timer();
    timer.schedule( new TimerTask(){
        public void run(){
            changeState();
            repaint();
        }
    },0,  10000 ); // 10 s. 
}

public void pause(){
    timer.cancel();
}

然后在您的“暂停/恢复”按钮中调用这些“暂停/开始绘画”方法:

if( e.getActionCommand().equals("Pause")){
    growPanel.pause();
    setText("Resume");
} else {
    growPanel.startPaiting();
    setText("Pause");
}

这是完整的源代码,以便查看其运行情况:

import javax.swing.*;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.*;
import java.util.Timer;
import java.util.TimerTask;

public class Grow {

    public static void main( String [] args ) {
        JFrame frame = new JFrame();
        final GrowPanel growPanel = new GrowPanel();
        frame.add( growPanel );
        frame.add( new JPanel(){{
            add( new JButton("Pause"){{
                addActionListener( new ActionListener(){
                    public void actionPerformed( ActionEvent e ){
                        if( e.getActionCommand().equals("Pause")){
                            growPanel.pause();
                            setText("Resume");
                        } else {
                            growPanel.startPaiting();
                            setText("Pause");
                        }
                    }
                });
        }});}}, java.awt.BorderLayout.SOUTH );
        frame.setSize( 400, 300 );
        frame.setVisible( true );
    }
}

class GrowPanel extends JComponent {
    private int x;
    private int y;
    private Timer timer;
    GrowPanel() {
        x = 10;
        y = 10;
        startPaiting();
    }

    public void startPaiting() {
        timer = new Timer();
        timer.schedule( new TimerTask(){
            public void run(){
                changeState();
                repaint();
            }
        },0,  100 ); // or 10000 which is 10 s. 
    }

    public void pause(){
        timer.cancel();
    }

    public void paintComponent( Graphics g ){
        g.fillOval( x, y, 10, 10 );
    }
    private void changeState(){
            x+=10;
            if( x >= 400 ) {
                y+=10;
                x = 0;
            }
            if( y >= 300 ){
                y = 10;
            }
    }

}

谢谢,这正是我所需要的。我在我的代码中遇到了一些问题,因为它的设置与你的有点不同。但我会努力解决的。谢谢。 - Luke
@Luke,我很高兴。你可以将我的答案标记为已接受(我告诉你这个是因为你是StackOverflow的新手:P) - OscarRyz
好的,有没有办法我可以把我的代码发给你?我现在正苦恼于如何将你给我的内容嵌入到我的代码中。我知道这对一个陌生人来说是很大的要求,如果这太麻烦了就没关系。 - Luke
当然可以,但是我认为如果您在这里创建一个新问题会更好。我可以查看那个或任何其他问题。可以这样表述:我问了这个问题 <链接到此问题>,我遇到了这个问题(描述您遇到的具体问题)。我想使用Oscar的解决方案,但我的代码不起作用。我的代码在这里<链接到pastebin.com>) 如果您有多个问题,请分别发布每个问题。我认为这样做可以更好地使其正常工作。 - OscarRyz

2
如果这些“按钮”是Swing按钮,那么实现这个功能的方法是:让“开始”按钮创建一个新的javax.swing.Timer对象,每10秒更新一次。然后让“暂停”按钮停止该计时器。

1

我个人更喜欢使用 Timer 而不是 Thread 或者 Thread.sleep()。定时器类可以同时处理周期性运行代码和取消执行。

你的代码应该像这样:

TimerTask myTask = new TimerTask() {
  public void run() {
    // here goes my code
  }
};

Timer timer = new Timer();
timer.schedule(myTask, 0 /*starts now*/, 10 * 1000 /* every 10 seconds */);

// whenever you need to cancel it:
timer.cancel();

1

你想在一个线程中运行你的模拟(查找Runnable接口)。 然后你可以向这个线程传递消息来暂停、继续和停止。


0

没错。看起来你是在事件分派线程上进行绘图。由于你从上一次调用中没有返回控制权,所以事件(例如按钮按下)永远没有机会被调用...


0
你需要的是多线程。将数据处理放入第二个线程中,异步运行,并使用主线程检查用户输入。
当然,这可能还涉及使用信号量在两个线程之间进行通信。你所在的课程级别如何?你已经学过这两个主题了吗?

啊啊啊,我在高中选修了Java课程,所以我的培训非常有限。我知道你在说什么,但我完全不知道如何将其实现为代码。谢谢。 - Luke

0

看一下wait() / notify()机制。简单来说,一个线程可以等待10秒钟,但仍然可以被通知外部事件已经发生。


0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接