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

我的Java(Tomcat 8)Web服务器是否可以安全地生成线程以响应HTTP请求? 我正在看post和论坛,有些人说这绝对没问题 ,其他人说不要这样做 。

我的用例是这样的:

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正在运行的线程可见。 有关详细信息,请参阅此文章 。

其次,如果你在这些其他线程中修改响应,不,这将是不安全的。 退出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 f1 = executor.submit(new Callable() { @Override public Result1 call() throws Exception { // do expensive stuff here. return result; } }); Future f2 = executor.submit(new Callable() { @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. 更先进的技术是异步处理 。 如果您的请求需要很长时间来处理,则使用异步处理是个好主意。 它不会改善任何一个请求的延迟,但它确实允许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(); } } 

此外,除了kuporific优秀答案之外,我还建议你考虑你的结果计算是否可以用列表或地图上的地图/filter/组操作来表达,因为在90%的情况下可以这样做。

如果是这种情况,我建议您使用本官方教程中概述的Java 8 Stream.parallelStreamfunction

如果您对如何/如何以这种方式表达您的计算感兴趣,请询问单独的问题

另外,回答你的初始问题 – 产生线程以便在任何地方(包括servlet) 并行计算是完全没问题 ,但我真的建议在优化之前和之后测量性能主要是因为这篇精湛文章中描述的原因

原则上,是的。 但是你必须留意你产生的线程总数,以确保你没有使用太多的资源。

使用线程池可以帮助控制线程数量。

我的想法是

  1. 如果线程应该在同一个请求中生效,那么这个想法就没有意义( 为了满足请求,请注意
  2. 当线程(运行/结束)的效果在下一个请求(特别是通过AJAX)中可见时, 可能有意义(如果正确完成)。 大多数servlet框架都有办法如何“以正统的方式”进行长时间的操作。

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

在我的构思中,线程或等价物可以从少数/罕见的请求开始,远远低于限制等,但如果是从每个请求完成,那么它是非常糟糕的设计。

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