【问题标题】:Good or bad idea: Multiple threads in a multi-user servlet-based web application with Java好主意或坏主意:使用 Java 的多用户基于 servlet 的 Web 应用程序中的多个线程
【发布时间】:2011-09-26 18:08:59
【问题描述】:

我目前正在构建一个基于 java-servlet 的 Web 应用程序,它应该为相当多的用户提供它的服务(不要问我“很多”是多少 :-) - 我还不知道) .

但是,在使用应用程序时,服务器端可能会发生一些耗时的处理。 为了避免糟糕的 UI 响应,我决定将这些处理操作移到它们自己的线程中。 这意味着一旦用户登录,可能会在后台运行 1-10 个线程(每个用户!)。

我曾经听说在 Web 应用程序中使用多个线程是一个“坏主意”。

这是真的吗?如果是的话:为什么?

更新:我忘了提到我的应用程序严重依赖 ajax 调用。每个用户操作都会导致一个新的 ajax 调用。因此,当主 servlet 线程忙时,ajax 调用需要很长时间来处理。这就是我想使用多个线程的原因。

【问题讨论】:

  • 为什么 UI 会“冻结”?听起来你在用原生术语思考......在网络应用程序中,你应该使用 AJAX,否则页面加载速度会很慢,而不是真正的“冻结”。
  • 我忘了提到我的应用程序严重依赖 ajax 调用。每个用户操作都会导致一个新的 ajax 调用。因此,当主 servlet 线程忙时,ajax 调用需要很长时间来处理。
  • 当然,但这与“UI 冻结”不同...
  • @Jon Skeet 你是对的。很抱歉造成误解。
  • “当主 servlet 线程忙时”是什么让你认为只有一个? Servlet 容器(通常)在来自池的单独线程中处理每个请求,并不是所有请求都有一个线程。

标签: java multithreading


【解决方案1】:

自己手动创建线程是个坏主意。这在 SO 中已经讨论了很多。例如,请参阅this 问题。

另一位question 讨论替代解决方案。

【讨论】:

  • 这对我来说似乎有点过于简单了。坏主意不仅仅是拥有非请求处理线程,而是以它们不会响应服务器关闭或其他生命周期事件的方式创建它们,或者在没有最大上限的情况下产生太多线程等。链接到的解决方案涉及使用 Quartz... 在后台产生线程。唯一的区别是使用广受好评的库而不是自己编写。
  • 你说得对,这个坏主意是指手动构建线程。我已经编辑了我的答案。
  • @matt b @kgiannakakis 我注意到大多数替代解决方案都需要我设置一个最大值。应用程序的线程数。什么是确定最大值的合理值的好方法。我需要多少线程?我正在运行一个多用户 Web 应用程序,我希望大多数用户产生 1-10 个线程(也许更多)。我不知道我最终会获得多少用户。
  • 平均用户数的测试和测量,对系统资源的拖累程度等
  • 值得注意的是,我遇到的几乎每个人都在错误地假设它意味着 Runnables=Threads 并且使用 Executors 等同于执行“new Thread()”的情况下呈现此建议。我可能弄错了,但我的理解是 Runnables 和 Executors 就在那里,所以我们不必做“new Thread()”,而我们应该避免做的是“new Thread()”。可运行!=线程
【解决方案2】:

“坏主意”不是多线程。 Java EE 最初是这样编写的,因此多线程由应用服务器控制,因此不鼓励用户启动自己的线程。

我认为您真正想要的是对长时间运行的任务进行异步处理,这样用户就不必等待它们完成后再继续。

您可以使用 JMS 做到这一点,并保持在 Java EE 着色书中的内容范围内。我认为你自己做会更安全,因为java.util.concurrent 包中有新的类和结构。

这仍然不是一件容易的事。多线程代码并非易事。但我认为它比以前在 Java 中更容易。

部分问题可能是您要求该 servlet 做太多事情。 Servlet 应该监听 HTTP 请求并协调从其他类获取响应,而不是自己完成所有处理。也许您的 servlet 告诉您是时候进行一些重构了。这将有助于您的测试,因为您无需运行 servlet/JSP 引擎即可对这些异步类进行单元测试。

通过 HTTP 对服务的 AJAX 调用不需要阻塞。如果服务可以返回一个令牌,例如 FedEx,它告诉应用程序何时以及如何获得响应,那么服务就没有理由不能异步处理。这是您应该对客户隐藏的服务的实现细节。

【讨论】:

    【解决方案3】:

    1.
    绝妙的主意。
    这并不常见,但没有错。
    如果您认为需要异步任务来获得更好的用户体验。随便用吧。

    2.
    你需要小心它。
    2.1.
    创建和销毁线程会增加服务器的大量开销。
    你最好使用一个执行器,比如java.util.concurrent.ThreadPoolExecutor

    2.2.
    不要只使用Executors.newFixedThreadPool()。它适用于初学者并隐藏了危险的细节。
    您需要了解 ThreadPoolExecutor 的边缘行为,并正确配置它。

    • 多少线程足以完成您的任务?你需要计算出来。
    • 如果您的池中没有空闲的头,会发生什么?不同的配置可以让它等待、缓存或放弃新任务。您应该期待什么?
    • 如果任务运行时间过长(如无限循环)会发生什么? java中没有真正的超时退出机制。你如何预防这些。

    【讨论】:

      【解决方案4】:

      如果应用程序需要它,那么我会说继续执行后台线程,但是,由于您不知道您将拥有多少用户,因此您冒着使服务器不堪重负的巨大风险。如果它们适用于您的情况,您可能会考虑一些替代方案。您可以完全离线运行后台任务吗?在批处理作业中?你能限制每个登录用户需要的线程数吗?您将如何将后台线程的结果返回给用户?

      【讨论】:

        【解决方案5】:

        这是一个坏主意,主要有三个原因:

        1. 过多的运行线程会杀死系统资源并导致一些奇怪的事情,例如饥饿和优先级倒置。这通常可以通过thread pool 解决。
        2. 用户会话持续时间不可预测。用户可以触发一个动作并去喝杯咖啡,或者他/她可能会抱怨延迟重做动作。这可能会导致创建多个后台作业,因此需要复杂的控制,而且当我们谈论线程时,我们永远无法确定我们是否没有离开竞争条件或未预料到的场景。
        3. servlet 很可能会与线程进行一些交互。现在假设您的应用程序需要扩展,因此您使用集群容器(毕竟,您有“很多”用户)。容器可以钝化会话并在另一个节点中恢复它。但是您的线程将保留在初始节点中,因此会话和线程之间的链接将被破坏。这会导致意外异常和错误 500 - 服务器故障。

        我认为最好的解决方案是设计您的应用程序,使其不会创建这么多后台线程。

        但如果您坚持或确实需要它,请尝试使用 Java EE message driven beans (MDB) 并让您的 servlet 使用 JMS 调用它,就像 @duffymo 所说的那样。

        挑战在于如何在 MDB 和用户会话之间进行通信。或许你的servlet可以创建一个JMSqueue或者topic发送给MDB让他们回复,但是我不知道JMS连接的servlet端是否可以钝化和恢复。

        另一种通信形式是 JNDI 或外部数据库或文件,但这需要轮询,这可能没有响应或 CPU 过多。

        【讨论】:

        • servlet 不会创建队列或主题;在应用程序启动之前在应用程序服务器中配置。 servlet(或者,更好的是,服务)将实例化一个客户端,该客户端将消息发送到队列或主题。
        • @duffymo 当然是关于客户的。实际上,servlet 将是一个客户端。现在对于队列,JMS 允许创建临时队列,如您所见 here
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-10-21
        • 1970-01-01
        • 2010-09-08
        • 1970-01-01
        • 2011-10-21
        • 1970-01-01
        相关资源
        最近更新 更多