我正在构建一个带有 GUI 和基于套接字的服务器的 Java 应用程序,并且我经常遇到应用程序的一部分陷入等待另一部分的问题(主要是 GUI 等待服务器 - 不)令人惊讶的是。有几次我设法避免这些错误,但我发现自己在启动后几乎立即就到达了 main
方法的末尾。(应用程序可能会也可能不会继续运行,具体取决于是否有任何 GUI 是否可见,但我认为 main
方法在程序实际退出之前不应该返回...)
我对应用程序的要求如下:
- 它应该能够同时处理未指定数量的客户端
- 服务器和客户端之间的通信可以朝任一方向进行,而不必每隔一个方向进行;有时服务器会发送一堆消息并仅从某些客户端获得回复,有时则相反。
- 客户端连接永远不应该“太晚” - 只要服务器应用正在运行,serversocket 就需要持续接受连接。
- 在整个过程中,GUI 应该不受服务器和客户端相互等待的影响。 GUI 的更新通过其他对象(主要是模型)上的事件监听器进行,这些对象由后台线程更改。
我尝试过以下方法,但似乎无法正确执行。
- 1 个线程用于
main
方法及其创建的对象( Controller 、模型等)执行的“常规”工作。这是我有时会遇到问题的线程,因为它不保存在任何地方,并且过早地从main
返回。 - 使用
EventQueue.invokeLater(new Runnable() { ... });
我在 UI 线程上执行所有实际的 GUI 操作,但这些调用都不是“存活”线程,因此它们基本上只是在主线程之外异步工作。 - 1 个线程,让
ServerSocket
能够继续监听新连接。 - 每个客户端有1个线程,能够监听来自客户端的消息。我不确定这里是否还需要另一个线程,以便能够“无序”发送消息,即无需先等待接收消息。
我以前从未编写过(真正的)多线程应用程序,所以这对我来说是全新的领域。然而,我不相信这个问题以前没有成功解决过——甚至多次出现某种最佳实践。
它们是什么?对于这个应用程序来说,什么是好的架构?
最佳答案
这个问题有很多不同的答案,但我能想到的最好规则是你需要一个 UI 线程(你没有说你在 GUI 中使用什么,但你提到了 invokeLater
,所以我正在考虑 Swing),然后一个或多个线程用于处理客户端。不需要为每个客户端分配一个线程;使用java.nio
而是使用异步 I/O 类。您可能希望使客户端处理线程的总数可以在运行时配置;范围会相当小,比如一到四。
运行应用程序的计算机(如果它确实是服务器)可能能够处理 4 个(例如,双核计算机)到 16 个(四核四核)实际并发执行线程(显然,有些服务器级机器的核心数量比这还要多,但您明白了),当然,您正在与架构上运行的所有其他服务共享这些机器。因此,拥有大量线程只会导致大量上下文切换。上下文切换很便宜,但远非免费,如果可以避免,那么 CPU 可以更有效地做其他事情。
有关使用 NIO 编码以最少线程处理大量客户端的服务器应用程序的示例,您可以查看 Netty 的源代码。事实上,您甚至可以考虑仅使用 Netty 并围绕其 I/O 处理构建应用程序逻辑。
<小时/>旁注:
The app may or may not keep running, depending on if there is any GUI visible or not, but I though the main method wasn't supposed to return until the program is actually exiting...
main
一旦你让它结束就会结束。只要有未完成的正在运行的线程,JVM 就会继续运行。如果您希望 main 在退出之前等待其他线程,请使用 Thread#join
加入他们。 join
导致当前线程等待,直到您调用 join
的线程终止(join
的某些重载提供超时,以便调用线程可以恢复如果被调用的线程在给定的时间内没有终止)。比较不带参数运行它与带参数运行它时的以下输出(任何参数,参数的内容并不重要):
public class JoinExample implements Runnable {
public static final void main(String[] args) {
Thread t = new Thread(new JoinExample());
System.out.println("Starting thread");
t.start();
if (args.length > 0) {
System.out.println("Joining thread");
while (t.isAlive()) {
try {
t.join();
}
catch (InterruptedException ie) {
}
}
}
System.out.println("main exiting");
}
public void run() {
long stop = System.currentTimeMillis() + 2000;
System.out.println("Thread starting");
while (System.currentTimeMillis() < stop) {
// Sleep a mo
try {
Thread.currentThread().sleep(250);
}
catch (InterruptedException ie) {
}
System.out.println("Thread still running");
}
System.out.println("Thread stopping");
}
}
综上所述,您可能希望让 main
线程终止,因为 UI 线程将是 Swing 创建的事件调度程序线程。有关线程和 swing 的更多信息 here和 here .
关于java - 我真正需要多少个线程?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8778327/