Java Swing多线程。我的GUI冻僵了
免责声明:我并没有将我的程序用于任何恶意目的,即使它的名字是Spambot。我只是用它来练习 编辑:我的问题是,如果我按下一个按钮,GUI将冻结,因此在第一个按钮完成其工作之前,我无法按下另一个按钮。我该怎么做 我创建了一个类(SpambotGUI),它基本上是一个包含3个jbutton的JFrame。下面是它的代码:Java Swing多线程。我的GUI冻僵了,java,multithreading,swing,Java,Multithreading,Swing,免责声明:我并没有将我的程序用于任何恶意目的,即使它的名字是Spambot。我只是用它来练习 编辑:我的问题是,如果我按下一个按钮,GUI将冻结,因此在第一个按钮完成其工作之前,我无法按下另一个按钮。我该怎么做 我创建了一个类(SpambotGUI),它基本上是一个包含3个jbutton的JFrame。下面是它的代码: public class SpambotGUI extends JPanel implements ActionListener { private static fin
public class SpambotGUI extends JPanel implements ActionListener {
private static final long serialVersionUID = 1L;
static JButton button1 = new JButton("Spam first file");
static JButton button2 = new JButton("Spam second file");
static JButton button3 = new JButton("Stop");
public SpambotGUI() throws AWTException {
button1.addActionListener(this);
button2.addActionListener(this);
button3.addActionListener(this);
button1.setActionCommand("spam1");
button2.setActionCommand("spam2");
button3.setActionCommand("stop");
button1.setMnemonic(KeyEvent.VK_F7);
button2.setMnemonic(KeyEvent.VK_F8);
button3.setMnemonic(KeyEvent.VK_F9);
button3.setToolTipText("Stop the program");
add(button1, BorderLayout.WEST);
add(button2, BorderLayout.CENTER);
add(button3, BorderLayout.SOUTH);
}
public void actionPerformed(ActionEvent e) {
System.out.println(java.awt.EventQueue.isDispatchThread());
if ((e.getActionCommand()).equals("spam1")) {
try {
Spambot.Start("data/spambotLines1.txt");
} catch (FileNotFoundException | AWTException | InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
} else if ((e.getActionCommand()).equals("spam2")) {
try {
Spambot.Start("data/spambotLines2.txt");
} catch (FileNotFoundException | AWTException | InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
} else if ((e.getActionCommand()).equals("stop")) {
Spambot.stopped = true;
Spambot.thread.interrupt();
}
}
public static void CreateGUI() throws AWTException {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
SpambotGUI buttons = new SpambotGUI();
buttons.setOpaque(true);
frame.setContentPane(buttons);
frame.pack();
frame.setVisible(true);
}
public static void main(String args[]) throws Exception {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
CreateGUI();
} catch (AWTException e) {
e.printStackTrace();
}
}
});
}
}
我还有一个Spambot
类,它包括以下内容:在Start
方法中,我有一个循环,包含不相关的内容和thread.Sleep-s(我创建了一个名为thread的新线程(),这就是为什么它在SpambotGUI
中用小写字母t拼写的原因),循环应该一直运行,直到Spambot
中的stopped
布尔值为false。如果我按下GUI中的Stop
按钮,后者将设置为false。问题是当Start
中的循环运行时,我无法单击GUI中的任何按钮。之后我在互联网上读了这篇文章,我得出结论,我应该在这里使用多线程
问题是,我只是不知道它应该如何工作。我尝试在我的Spambot
类中实现Runnable
,然后从SpambotGUI
调用run()
方法,但没有任何改变
有人知道我应该在这里做什么吗
编辑:这里是Spambot
类的一部分:
public class Spambot{
private static Robot robot;
public static Thread thread = new Thread();
public static boolean stopped = false;
public static void main(String... args) throws Exception {
}
public static void Start(String path) throws AWTException, InterruptedException, FileNotFoundException {
Scanner input = new Scanner(new FileReader(path));
Spambot keyboard = new Spambot();
Random rand = new Random();
robot.keyPress(KeyEvent.VK_ALT);
thread.sleep(150);
robot.keyPress(KeyEvent.VK_TAB);
thread.sleep(150);
robot.keyRelease(KeyEvent.VK_TAB);
robot.keyRelease(KeyEvent.VK_ALT);
thread.sleep(500);
while (input.hasNextLine() && !stopped) {
keyboard.type(input.nextLine());
thread.sleep(rand.nextInt(1500)+1000);
robot.keyPress(KeyEvent.VK_ENTER);
robot.keyRelease(KeyEvent.VK_ENTER);
}
input.close();
}
public Spambot() throws AWTException {
Spambot.robot = new Robot();
}
public Spambot(Robot robot) {
Spambot.robot = robot;
}
}
您应该在任何GUI应用程序中使用的不是多线程,而是事件驱动编程。这意味着永远不要在事件处理程序中执行长时间运行的循环,永远不要在一个事件处理程序中调用Thread.sleep
相反,必须在Swing计时器上调度延迟的GUI操作,并且必须将循环体放入您提交给计时器的处理程序中
如果您没有延迟的操作,但确实是一个长时间运行的任务(这意味着您需要进行大量计算或等待I/O),只有这样,您才需要一个后台线程来完成这项工作。在这种情况下,您可以使用SwingWorker
将任务的结果传回GUI。在任何GUI应用程序中,您应该使用的不是多线程,而是事件驱动编程。这意味着永远不要在事件处理程序中执行长时间运行的循环,永远不要在一个事件处理程序中调用Thread.sleep
相反,必须在Swing计时器上调度延迟的GUI操作,并且必须将循环体放入您提交给计时器的处理程序中
如果您没有延迟的操作,但确实是一个长时间运行的任务(这意味着您需要进行大量计算或等待I/O),只有这样,您才需要一个后台线程来完成这项工作。在这种情况下,您将使用SwingWorker
将任务的结果传回GUI。我认为您必须了解,您的程序逻辑和GUI
绝不应该在同一个线程上运行。当您的程序忙于执行任务时,您不希望您的GUI
冻结。Java
解决这个问题的方法是事件调度线程(EDT)。
你这样做:
public static void main(String args[]) {
EventQueue.invokeLater(new Runnable() {
public void run() {
//Your GUI code here
}
});
}
有关EDT的更多信息,请参见此处:我认为您必须了解,您的程序逻辑和GUI
不应在同一线程上运行。当您的程序忙于执行任务时,您不希望您的GUI
冻结。Java
解决这个问题的方法是事件调度线程(EDT)。
你这样做:
public static void main(String args[]) {
EventQueue.invokeLater(new Runnable() {
public void run() {
//Your GUI code here
}
});
}
有关EDT的更多信息,请参见此处:您没有使用任何线程,而只是使用Thread.sleep()方法。这只是一个正常的睡眠程序。因此,在start1和start2中完成操作之前,GUI将被阻止 您没有使用任何线程,而只是使用Thread.sleep()方法。这只是一个正常的睡眠程序。因此,在start1和start2中完成操作之前,GUI将被阻止 您可能实际上必须启动一个新的线程
,因此阻塞操作不会对应用程序GUI造成太大影响。但是,更新GUI中的操作应该由原始事件调度线程执行
在这里,主要的问题似乎是使用Thread.sleep()
。当在事件分派线程中执行时,将导致GUI变得不负责任(在完成事件侦听器代码的执行之前不会接受您的输入或重画)。但是,如果在其他线程中使用Thread.sleep()则是可以接受的(这不会冻结您的GUI)
怎么做
首先:在单独的线程中启动阻塞处理代码
public void actionPerformed(ActionEvent e) {
if ((e.getActionCommand()).equals("spam1")) {
new Thread(){
@Override
public void run() {
try {
Spambot.Start("data/firstfile.txt");
} catch (FileNotFoundException | InvocationTargetException |
AWTException | InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}.start();
}
// ... rest of conditions
}
其次,延迟之间的每个GUI更新都应该在事件调度线程中完成
EventQueue.invokeAndWait(new Runnable(){
public void run() {
robot.keyPress(KeyEvent.VK_ALT);
};
});
由于所有更新都是在robot.keyPress()
调用中完成的,因此一个好的选择可能是封装并在方法中重用。请注意,内部类中使用的局部变量和参数应定义为final(因此它们在方法的stackframe之外可用)
编辑:Oops。我和SwingWorker搞错了。实际上可能已经足够了。
注意:Swing中有一些帮助器组件,可以使我们免于复杂且容易出错的线程处理。实际上,您可能会使用一个函数,其中被重写的doInBackground()
方法(在工作线程中)遍历文件,执行暂停,并发送击键(调用publish(Integer)
),由EDT在被重写的进程(列表)中处理
方法。您可能实际上必须启动一个新的线程
,因此阻塞操作不会对应用程序GUI造成太大影响。但是,更新GUI中的操作应该由原始事件调度线程执行
作为,