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
对象。这不是线程安全的,因为 first
和 second
线程所做的可能对彼此不可见,或者对 servlet 运行 所在的线程不可见。有关详细信息,请参阅 this article。
其次,如果您正在修改这些其他线程中的响应,不,这将不安全。退出 doGet
方法后,您应该考虑发送的响应。在您的示例中,有可能在这两个线程 运行.
之前将响应发送回客户端
假设 MyResult result
影响 response
对象(您要么将 result
添加到 response
,它会影响响应代码,等等)。有几种方法可以解决这个问题。
使用ExecutorService
和Future
:
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
}
更高级的技术是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
集成到服务器中,可以透明地将请求状态传播到托管线程。
在我看来
- 这个想法没有是否线程应该在同一个请求中生效(cit.为了满足请求)
- 可能有意义(如果正确完成)当线程(运行/结束)的效果在下一个请求中可见时(特别是通过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
我的 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
对象。这不是线程安全的,因为 first
和 second
线程所做的可能对彼此不可见,或者对 servlet 运行 所在的线程不可见。有关详细信息,请参阅 this article。
其次,如果您正在修改这些其他线程中的响应,不,这将不安全。退出 doGet
方法后,您应该考虑发送的响应。在您的示例中,有可能在这两个线程 运行.
假设 MyResult result
影响 response
对象(您要么将 result
添加到 response
,它会影响响应代码,等等)。有几种方法可以解决这个问题。
使用
ExecutorService
和Future
: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 }
更高级的技术是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
集成到服务器中,可以透明地将请求状态传播到托管线程。
在我看来
- 这个想法没有是否线程应该在同一个请求中生效(cit.为了满足请求)
- 可能有意义(如果正确完成)当线程(运行/结束)的效果在下一个请求中可见时(特别是通过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