Java servlet 生成线程以满足请求是否安全?

Is it safe for a Java servlet to spawn threads in order to satisfy a request?

我的 Java (Tomcat 8) Web 服务器生成线程以响应 HTTP 请求是否安全?我在帖子和论坛上看到有人说它是 absolutely fine, and others say not to do it

我的用例是这样的:

 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    ...
    ...
    final MyResult res = new MyResult();
    Thread first = new Thread(new Runnable() {
         @Override
         public void run() {
             // put this into res
         }
     });
     Thread second = new Thread(new Runnable() {
         @Override
         public void run() {
             // put that into res
         }
     });
     first.start();
     second.start();
     first.join(10000);
     second.join(10000);

     // return res
 }

当我说安全时,我的意思是我所提议的关于 Web 服务器稳定性的内容是否存在任何内在的危险。正如@Burrman 指出的那样,线程池在这里是个好主意,我会这样做。如果我使用的是线程池,那么我应该关注或需要解决的 servlet 容器还有其他潜在问题吗?

我想我想的是,例如,JDBC 连接。我相信建议使用 JNDI 资源等进行设置,并使用 Tomcat 配置进行配置。像我的示例中那样生成任意线程是否需要或推荐类似的东西?

原则上可以。但是您必须密切关注正在生成的线程总数,以确保您没有使用太多资源。

使用线程池有助于控制线程数量。

首先,您似乎正在两个线程中修改 result 对象。这不是线程安全的,因为 firstsecond 线程所做的可能对彼此不可见,或者对 servlet 运行 所在的线程不可见。有关详细信息,请参阅 this article

其次,如果您正在修改这些其他线程中的响应,不,这将不安全。退出 doGet 方法后,您应该考虑发送的响应。在您的示例中,有可能在这两个线程 运行.

之前将响应发送回客户端

假设 MyResult result 影响 response 对象(您要么将 result 添加到 response,它会影响响应代码,等等)。有几种方法可以解决这个问题。

  1. 使用ExecutorServiceFuture:

    public void doGet(HttpServletRequest request, HttpServletResponse response) {
       // Creating a new ExecutorService for illustrative purposes.
       // As mentioned in comments, it is better to create a global 
       // instance of ExecutorService and use it in all servlets. 
       ExecutorService executor = Executors.newFixedThreadPool(2);
    
       Future<Result1> f1 = executor.submit(new Callable<Result1>() {
          @Override
           public Result1 call() throws Exception {
              // do expensive stuff here.
              return result;
          }
       });
    
       Future<Result2> f2 = executor.submit(new Callable<Result2>() {
          @Override
          public Result2 call() throws Exception {
             // do expensive stuff here.
             return result;
          }
       });
    
       // shutdown allows the executor to clean up its threads. 
       // Also prevents more Callables/Runnables from being submitted.
       executor.shutdown();
    
       // The call to .get() will block until the executor has
       // completed executing the Callable.
       Result1 r1 = f1.get();
       Result2 r2 = f2.get();
       MyResult result = new MyResult();
       // add r1 and r2 to result.
       // modify response based on result
    }
    
  2. 更高级的技术是Asynchronous Processing。如果您的请求需要很长时间才能处理,那么使用异步处理是个好主意。它不会改善任何一个请求的延迟,但它确实允许 Tomcat 在任何给定时间点处理更多请求。

    一个简单的例子是:

    @WebServlet(urlPatterns={"/asyncservlet"}, asyncSupported=true)
    // Rather than @WebServlet, you can also specify these parameters in web.xml    
    public class AsyncServlet extends HttpServlet {
       @Override
       public void doGet(HttpServletRequest request, HttpServletResponse response) {
          response.setContentType("text/html;charset=UTF-8");
          final AsyncContext acontext = request.startAsync();
          acontext.start(new Runnable() {
             public void run() {
                // perform time consuming steps here.
                acontext.complete();
       }
    }
    

JEE 服务器将一些信息存储在线程局部变量中。例如。 JAAS 的安全上下文、JTA 事务...新的普通 java 线程无法访问此类信息。 AsyncContext 和 JEE ExecuterService 集成到服务器中,可以透明地将请求状态传播到托管线程。

在我看来

  1. 这个想法没有是否线程应该在同一个请求中生效(cit.为了满足请求
  2. 可能有意义(如果正确完成)当线程(运行/结束)的效果在下一个请求中可见时(特别是通过AJAX ).大多数 servlet 框架都有如何执行 "in orthodox way" 长操作的方法。

要明确:这不是执行典型日常 AJAX 任务的正常方式(必须将设计划分为 "main" 请求和下一个子 ajax 请求,使用您的框架),但是AJAX可以呈现后台长线程的状态。

在我的概念中,线程或等效项可以从 few/rare 请求启动,远低于限制等,但如果它是从每个请求完成的,那是非常糟糕的设计。

此外,除了 kuporific 的出色回答外,我真的建议您考虑一下您的结果 computation 是否可以用 map / filter 来表示/ group operations on lists or maps, 因为在 90% 的情况下都可以这样做。

如果是这种情况,我真的会建议您使用 Java 8 Stream.parallelStream 中概述的功能 this official tutorial

如果您感兴趣,请单独提问if/how可以用那种方式表达您的计算

此外,回答您最初的问题 - 在任何地方(包括 servlet)生成线程来并行计算 是完全没问题的,但是我真的建议 衡量优化前后的性能主要是因为描述的原因in this superb article