我想多线程化我的GAE servlet,以便同一个实例上的同一个servlet可以处理多达10个(在前端实例上我相信 max#threads为10)来自不同的并发请求用户同时在每个用户之间进行计时。
public class MyServlet implements HttpServlet {
private Executor executor;
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) {
if(executor == null) {
ThreadFactory threadFactory = ThreadManager.currentRequestFactory();
executor = Executors.newCachedThreadPoolthreadFactory);
}
MyResult result = executor.submit(new MyTask(request));
writeResponseAndReturn(response, result);
}
}
所以基本上当GAE启动时,第一次收到对这个servlet的请求时,会创建一个Executor
然后保存。然后每个新的servlet请求使用该执行器来生成一个新线程。显然MyTask
内的所有内容都必须是线程安全的。
我关心的是这是否真正做到了我希望它做的事情。也就是说,这段代码是否创建了一个非阻塞的servlet,它可以同时处理来自多个用户的多个请求?如果没有,为什么以及我需要做些什么来解决它?而且,一般来说,GAE大师可以发现其他任何错误吗?提前谢谢。
答案 0 :(得分:6)
我认为你的代码不会起作用。
doGet
方法在servlet容器管理的线程中运行。当请求进入时,servlet线程被占用,并且在doGet
方法返回之前不会释放它。在您的代码中,executor.submit
将返回Future
个对象。要获得实际结果,您需要在get
对象上调用Future
方法,它将阻塞,直到MyTask
完成其任务。只有在那之后,doGet
方法才会返回并且新请求可以启动。
我不熟悉GAE,但根据their docs,您可以将您的servlet声明为线程安全,然后容器将并行地向每个Web服务器分派多个请求:
<!-- in appengine-web.xml -->
<threadsafe>true</threadsafe>
答案 1 :(得分:5)
你含蓄地问了两个问题,让我回答两个问题:
<强> 1。如何让我的AppEngine实例处理多个并发请求?
你真的只需做两件事:
<threadsafe>true</threadsafe>
添加到appengine-web.xml
文件中,您可以在war\WEB-INF
文件夹中找到该文件。doGet(...)
,doPost(...)
等方法中的局部变量或make确保您同步对类或全局变量的所有访问。这将告诉AppEngine实例服务器框架您的代码是线程安全的,并且允许它在不同的线程中多次调用所有请求处理程序来处理多个请求时间。注意:AFAIK,无法基于每个servlet设置此值。所以, ALL 你的servlet需要是线程安全的!
因此,实质上,您发布的执行程序代码已包含在每个AppEngine实例的服务器代码中,并实际从AppEngine创建的单独线程的run方法内部调用doGet(...)
方法(或重用每个请求。基本上doGet()
已经 <{1}}。
文档的相关部分在这里(虽然它并没有多说):https://developers.google.com/appengine/docs/java/config/appconfig#Using_Concurrent_Requests
<强> 2。发布的代码对此(或任何其他)目的有用吗?
当前表单中的AppEngine不允许您创建和使用自己的线程来接受请求。它只允许你使用你提到的MyTask()
方法在你的doGet(...)
处理程序中创建里面的线程,但只对这一个请求进行并行处理而不接受第二个请求一个并行(这发生在 currentRequestThreadFactory()
之外)。
名称doGet()
可能会有点误导。这并不意味着它将返回currentRequestThreadFactory()
的{{1}} current
,即处理请求的线程。这意味着它返回Factory
,可以在RequestThreads
内创建Factory
。所以,遗憾的是,实际上甚至不允许在当前Threads
执行范围之外使用返回的ThreadFactory,就像你建议的那样,创建一个基于它的Executor并将其保存在类变量中。
对于前端实例,当currentRequest
方法返回时,您在doGet()
调用中创建的任何线程都会立即终止。对于后端实例,您可以创建继续运行的线程,但由于您不允许打开服务器套接字来接受这些线程内的请求,因此这些仍然不允许您自己管理请求处理。
您可以在此处找到有关 和无法在appengine servlet中执行的操作的更多详细信息:
The Java Servlet Environment - The Sandbox(特别是主题部分)
为了完整起见,让我们看看您的代码如何变得“合法”:
以下内容应该可行,但在代码能够并行处理多个请求方面不会产生任何影响。这将仅由您的appengine-web.xml中的doGet()
设置决定。因此,从技术上讲,这段代码实际上效率很低,并且在两个线程之间划分了基本上线性的程序流。但无论如何它在这里:
doGet()
由于您已经在一个特定于您当前正在处理的请求的单独线程中,因此您应该自己保存“线程内部的线程”并简单地执行此操作:
<threadsafe>true</threadsafe>
或者,更好的是,只需将代码从MyTask.call()移动到doGet()方法即可。 ;)
除此之外 - 关于您提到的10个并发servlet线程的限制:
这是一个(临时)设计决策,允许Google更轻松地控制服务器上的负载(特别是servlet的内存使用)。
您可以在此处找到有关这些问题的更多讨论:
这个话题一直困扰着我,因为我坚信超精简servlet代码,所以我常用的servlet可以轻松处理数百个(如果不是数千个)并发请求。由于每个实例的10个线程的任意限制而必须支付更多实例,至少可以说有点烦人。但阅读上面发布的链接,听起来他们已经意识到这一点,并正在努力寻找更好的解决方案。那么,让我们看看Google I / O 2013将在5月份发布什么声明...... :)
答案 2 :(得分:2)
我对 ericson 和 Markus A的评估进行了第二次评估。
但是,如果出于某种原因(或某些其他情况),您希望遵循使用代码段作为起点的路径,我建议您将执行程序定义更改为:
private static Executor executor;
使其在实例之间变为静态。