【问题标题】:How many threads do I really need?我真的需要多少线程?
【发布时间】:2012-01-08 22:57:01
【问题描述】:

我正在构建一个带有 GUI 和基于套接字的服务器的 Java 应用程序,并且我经常遇到应用程序的一部分卡住等待另一部分的问题(主要是 GUI 等待服务器 - 不令人惊讶。有几次我设法避免了这些错误,相反,我发现自己在启动后几乎立即到达了main 方法的末尾。(应用程序可能会或可能不会继续运行,这取决于是否有任何 GUI 可见或不是,但我认为 main 方法不应该在程序实际退出之前返回......)

我对申请的要求如下:

  • 它应该能够同时处理未指定数量的客户端
  • 服务器和客户端之间的通信可以双向进行,不一定每隔一圈;有时服务器会发送一堆消息并仅从某些客户端获得回复,有时则相反。
  • 客户端连接永远不会“为时已晚” - 只要服务器应用程序正在运行,服务器套接字就需要不断地接受连接。
  • 在整个过程中,GUI 应该不受服务器和客户端相互等待的影响。通过后台线程更改的其他对象(主要是模型)上的事件侦听器对 GUI 进行更新。

我尝试了以下方法,但似乎无法正确。

  • main 方法的 1 个线程和它创建的对象(控制器、模型等)执行的“常规”工作。这是我有时会遇到问题的线程,因为它在任何地方都存在并且过早地从main 返回。
  • 使用EventQueue.invokeLater(new Runnable() { ... });,我在 UI 线程上执行所有实际的 GUI 操作,但这些调用都不是“幸存的”线程,因此它们基本上只是在主线程之外异步工作。
  • ServerSocket 的 1 个线程能够继续侦听新连接。
  • 每个客户端有 1 个线程,以便能够监听来自客户端的消息。我不确定这里是否还需要另一个线程,以便能够“乱序”发送消息,即无需等待先接收消息。

我以前从未编写过(真正的)多线程应用程序,所以这对我来说是全新的领域。但是,我不相信这个问题以前没有成功解决过 - 甚至很多次都已经发展出某种最佳实践。

它们是什么?此应用程序的良好架构是什么?

【问题讨论】:

    标签: java multithreading sockets user-interface


    【解决方案1】:

    这个问题有很多不同的答案,但我能想到的最好的规则是你需要一个 UI 线程(你没有说你在 GUI 上使用什么,但你提到了invokeLater,所以我在想 Swing),然后是一个或多个线程来处理客户端。每个客户端都不需要一个线程;改为使用 java.nio 类进行异步 I/O。您可能希望使客户端处理线程的总数可以在运行时配置;范围会很小,比如一到四个。

    运行应用程序的机器(如果它真的是服务器)可能能够处理四个(例如,双双核机器)到十六个(四核四核)实际并发执行线程(显然,服务器级机器的内核比这更多,但你明白了),当然,你正在与架构上运行的所有其他服务共享这些机器。因此,拥有大量线程只会导致大量上下文切换。上下文切换很便宜,但远非免费,如果可以避免的话,CPU 可以更有用地做其他事情了。

    有关使用 NIO 编码以使用最少线程处理大量客户端的服务器应用程序示例,您可以查看Netty 的源代码。事实上,您甚至可以只考虑使用 Netty 并围绕其处理 I/O 构建应用程序逻辑。


    旁注:

    应用程序可能会或可能不会继续运行,这取决于是否有任何可见的 GUI,但我认为 main 方法不应该在程序实际退出之前返回......

    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 创建的事件调度程序线程。有关线程和摆动的更多信息 herehere

    【讨论】:

    • 我从未听说过 java.nio.*(毕竟,我唯一真正的非牛仔 Java 教育是我本科第一年的基础编程课程......出于方便而使用Java。当然是老师的方便……)但是在阅读了一点之后,这绝对是要走的路。谢谢!
    猜你喜欢
    • 2011-08-18
    • 1970-01-01
    • 1970-01-01
    • 2016-03-20
    • 2023-03-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多