Java 如何处理doGet或doPost中的潜在长时间操作?

Java 如何处理doGet或doPost中的潜在长时间操作?,java,multithreading,web-applications,servlets,timeout,Java,Multithreading,Web Applications,Servlets,Timeout,在一个HTTP帖子(由应用程序生成,而不是由用户生成)之后,我想发送一封电子邮件。我正确地完成了电子邮件发送过程,但我不确定JavaWebApp服务器是如何工作的 我特别担心超时,我想知道我是否以某种方式阻止了一个重要的线程 如果我这样做: @Override public void doPost( final HttpServletRequest req, final HttpServletResponse resp )

在一个HTTP帖子(由应用程序生成,而不是由用户生成)之后,我想发送一封电子邮件。我正确地完成了电子邮件发送过程,但我不确定JavaWebApp服务器是如何工作的

我特别担心超时,我想知道我是否以某种方式阻止了一个重要的线程

如果我这样做:

    @Override
    public void doPost(
            final HttpServletRequest req,
            final HttpServletResponse resp
    ) throws IOException, ServletException {
        final PrintWriter pw = resp.getWriter();
        pw.write( ... );
        pw.flush();
        pw.close();
        // Here I'm sending an email, this can potentially
        // block until the email send procedure times out
        // (the timeout is set to 5 seconds)
        sendEmail(...);
    }
如果电子邮件服务器关闭,线程将阻塞,直到我的sendEmail超时(超时时间设置为几秒钟)

然后我阻塞了哪个线程?我的意思是,显然我意识到我正在阻止处理这篇文章的线程,但这是一个问题吗?这个线程下一步应该做什么

我读到我不应该从JavaWebApp服务器创建新线程,所以我认为我不应该这样做,对吗

    Thread t = new Thread( new Runnable() {
        public void run() {
            sendEmail();
        }
    });
    t.start();

请注意,我的问题并不特定于电子邮件发送:我想了解每次您计划在GET或POST之后执行潜在的阻止/长时间操作时,Java webapp中必须注意的事项。

我的建议:存储您需要执行的任务(即在数据库或队列中)通过第二个进程在后台处理它们,并使您的
doPost
/
doGet
尽快返回。用户不想等待

例如,您可以接收外部应用程序请求,将需要发送的电子邮件存储在数据库中,或将其放入JMS队列(许多应用程序服务器都有JMS功能,但我从未使用过),然后返回。另一个过程可能是读取数据库/队列并发送电子邮件而不阻止HTTP响应


关于在你的webapp中使用线程,它会起作用,可能是最简单的解决方案,但也可能存在可伸缩性问题。如果这样做,请确保使用某种类型的线程池(
ExecutorService
…),因为web服务器/操作系统通常对线程数量有限制。

在servlet中启动线程是可以的,您只需非常小心地确保在servlet未部署时线程死亡

最简单的方法是在servlet启动时创建
java.util.concurrent.ExecutorService
,并在servlet被销毁时关闭它。然后,您可以向executor服务提交电子邮件作业,并从
doPost
返回。请注意,如果在作业排队后servlet被破坏,则可能无法发送某些电子邮件

代码:

class EmailServlet extends HttpServlet {
    private ExecutorService emailSender;

    public void init() {
        emailSender = Executors.newFixedThreadPool(1);
    }

    public void destroy() {
        emailSender.shutdownNow();
    }

    public void doPost(...) {
        ...
        emailSender.execute(new Runnable() {public void run() {sendEmail();}});
    }
}

例如,我可以将来自所有帖子的电子邮件请求放在一个队列中,并让另一个(唯一的)线程将其出列并发送。但这意味着我必须至少产生一个其他线程。这样做可以吗?在我的例子中,执行POST的不是用户而是另一个应用程序。但是,不管怎样,这些帖子是由用户发送的,用户不是在我关闭流后就可以得到页面吗?(在尝试发送电子邮件之前我会这样做)是的,但即使应用程序得到响应,您的服务器仍可能面临可伸缩性问题。这可能取决于服务器配置,但是想象一下,如果您的服务器配置为支持500个同时请求,而您的插槽已经用完,因为所有这些请求都在发送电子邮件,那么会发生什么情况……明白了!这是一个非常简单的用例,所以我会尽量保持简单。我曾考虑只使用另一个应用程序来消费/发送电子邮件(电子邮件中的文本无论如何都存储在DB中),但我想知道是否可以通过直接从Webapp中使用它来保持它的“小”度(从而使我不必构建/部署/维护两个应用程序)。我将确保只使用一个线程发送所有这些电子邮件,并在.war取消部署时正确停止该线程。非常感谢。太好了,尽量保持简单。什么时候取消部署servlet?例如,当我取消部署并重新部署.war时?如果我不确定它是否会死掉,那么每次我在不重新启动Tomcat的情况下取消部署/重新部署我的.war时,Tomcat将有越来越多的线程不再执行任何操作?(我理解正确吗?;)这取决于容器,但是取消部署和重新部署。war应该调用
destroy
。您可能可以依靠容器正确地完成工作(但登录以确保这一点不会有什么坏处)。如果在一个servlet实例上有两个调用
init
,我会非常惊讶,因为这不符合servlet规范。