我已经完成了一个GUI应用程序。我有四个班级:Main, UserWindow, Task1, Task2
。 Main
类包含布尔变量buttonStartPressed
。 Main
方法启动UserWindow
类的实例,并等待用户按下“开始”按钮。当用户按下“开始”按钮(在UserWindow
中)时,ActionListener
会将true
分配给static boolean buttonStartPressed
,并且Main
方法会继续。
Main.java
public static void ......
static boolean buttonStartPressed = false;
...........
while (!buttonStartPress) {
Thread.sleep(50);
}
Task1 t1 = new Task1();
.....
}
}
工作正常,但我不喜欢while
循环。我觉得这不是编写应用程序的传统方式。还有另一种方法:我可以组合Main
和UserWindow
类,ActionListener
(buttonPressed)的结果将是Task1的开始。但是,另一方面,我认为Main
类和UserWindow
类应该彼此分开。
答案 0 :(得分:3)
你甚至需要buttonStartPressed变量吗?为什么你有兴趣知道它是否被按下,按下按钮时执行某些动作的主要想法不是?
您应该在actionPerformed()
方法中创建Task1,并根据您要执行的操作,启动一个执行任务的线程(或者如果它真的很快就在EDT中运行它做,所以它不会冻结GUI)。
答案 1 :(得分:2)
Swing(几乎每个GUI工具包)都有一个专用线程。这个线程,即事件调度线程,在您第一次需要它时启动。这通常是您setVisible
JFrame
时的情况。这个线程是一个巨大的循环,其作用是消耗输入事件并重新绘制事件并相应地运行一些逻辑。
在你的情况下,你实际上有两个线程。第一个是main
线程,第二个是EDT
。您的main
主题正在执行busy wait。一旦用户按下按钮,actionListener
的代码就会在EDT
中执行。
您正在使用布尔变量作为使两个线程进行通信的方法。使用某些共享内存确实是inter-thread communication的一种可能方式。
现在,你怀疑你应该避免忙碌等待。它无用地消耗CPU时间,每次唤醒时都会扰乱其他线程,并且它有不可避免的反应延迟。
使用共享内存进行通信通常也很糟糕。这是一种过于低级的沟通方式,而且往往做错了。必须通过锁定机制保护对来自两个线程的数据的访问。即使像布尔一样简单的数据也可以咬你,因为无法保证如果一个线程写入它,另一个线程将看到修改。在您的示例中,布尔值应至少声明为volatile
以获得此保证。
因此,添加volatile
关键字后,您的解决方案就可以了:您有一个EDT
正在忙着做这些事情,当用户点击按钮时,main
线程会执行{ {1}}。问自己的第一个问题是:Task1
是一项耗时的任务吗?实际上,最简单的解决方案是在Task1
中通过从EDT
调用Task1来运行Task1。请注意,在actionListener
中执行某些代码会冻结GUI。如果EDT
持续时间少于100毫秒,则用户甚至不会注意到冻结,并且在另一个线程中执行它没有意义。如果您担心将GUI类与“任务”类耦合,那么您应该使用观察者模式来防止直接依赖。
如果任务耗时并且您不希望GUI冻结,那么您应该使用多个线程。一个解决方案是您实施的解决方案。它有点受限,因为你只有一个Task1
线程,但它有效。你现在的问题是让这些线程进行通信。线程间通信中一个非常常见的模式是使用blocking queue。这也是一个共享数据,但它被设计为由多个线程使用。一个线程(main
)写入(EDT
),另一个线程从中读取(add()
)并阻塞,直到写入内容为止。对于您的简单示例而言,这似乎有些过分,但它是在线程之间共享数据的一种非常方便的方法。写入阻塞队列的对象可以是任何东西;例如,它们可以表示要执行的命令。
从GUI执行耗时功能的更常规方法是在需要时创建或使用专用线程。这可以使用低级API(Thread)或使用更高级别的API(ExecutorService)来完成,两者都非常易于使用。同样,如果要将GUI操作与线程创建分离,请使用观察者模式。
我很抱歉,如果这个文本墙不能为您的问题提供简单的答案,但是当我们混合使用GUI和线程时,有很多事情需要考虑。我希望对你有用,了解你的其他选择。