对于Servlet自己的生命周期我们这里不谈了,本文主要想测试一下Tomcat中结合多线程,Servlet实例化过程是怎样的。
写第一个demo servlet
public class DemoServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private String time = "first";
@Override
public void init() throws ServletException {
System.out.println("DemoServlet.init()");
}
@Override
public void destroy() {
System.out.println("DemoServlet.destroy()");
}
public DemoServlet() {
System.out.println("DemoServlet.DemoServlet()");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println(Thread.currentThread().getId());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
response.getWriter().println("<html><body><h3>Welcome "+time+"</h3></body></html>");
time = "second";
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
连续访问 http://localhost:8080/test-servlet/DemoServlet 两次,
console里得到如下的输出:
DemoServlet.DemoServlet()
DemoServlet.init()
18
18
浏览器里的两个页面得到的输出分别是:
Welcome first
和
Welcome second
分析一下:
1. console里DemoServlet.init()和DemoServlet.DemoServlet()都分别只打印了一次,说明Servlet只实例化了一次,我们在浏览器里看到的情况也应证了这一点,这个Servlet里的time变量是同一个。
2. console里的18是当前线程的ID,因为线程的ID在同一个jvm里是唯一的,所以这两次访问是同一个线程。而浏览器打开页面的响应时间也正好证明了这一个,第一个页面消耗量大约5s,第二个页面大约消耗量10s,因为是同一个线程,所以要排队等待。
分析到这里我有一点收获就是我之前yy的Tomcat决定何时开启一个新的线程应该是要看当前池里有没有空闲线程,如果没有那么要新开一个,从这里我看不是,因为明显这唯一的线程当时卡在了doGet方法里,表现为是忙碌的。
再往下想,因为是在同一个线程里那么Servlet实例只有一个也是理所当然了。
这时我很好奇,何时才开启新的线程呢?google里一下,发现有人这么测试,说是测出多线程了,我也测了一下,并加入了线程ID的打印语句,发现确实是多线程
public class DemoServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
public void init() throws ServletException {
System.out.println("DemoServlet.init()");
}
@Override
public void destroy() {
System.out.println("DemoServlet.destroy()");
}
public DemoServlet() {
System.out.println("DemoServlet.DemoServlet()");
}
PrintWriter output;
public void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println(Thread.currentThread().getId());
String username;
response.setContentType("text/html; charset=gb2312");
username = request.getParameter("username");
output = response.getWriter();
try {
Thread.sleep(5000); // 为了突出并发问题,在这设置一个延时
} catch (InterruptedException e) {
}
output.println(" 用户名:" + username + "<BR>");
}
}
用浏览器同时访问 http://localhost:8080/test-servlet/DemoServlet?username=a 和 http://localhost:8080/test-servlet/DemoServlet?username=b
得到console里的输出:
DemoServlet.DemoServlet()
DemoServlet.init()
18
19
再看浏览器:
第一个页面是空白
第二个页面是
用户名:a
用户名:b
是不是有点奇怪呢,怎么第一个页面的内容跑到第二个页面里来了?分析一下就清楚了,首先看两个线程ID不同,很显然是开启了新的线程了。而init只调用了一次,说明使用的是同一个Servlet实例,进而我们知道output成员变量肯定也是同一个引用了,那么再两个线程里同时交叉执行了service方法。
说到这里我先插一句:在两个线程中同一实例的成员变量也是同一个,而在方法里的临时变量却是不同的,临时的嘛,虽然是同一个方法,但在不同的执行环境下当然就是两个东西了。
好了,再分析service方法的执行。第一个线程先执行,得到用户名为"a",并将output引用指向了当前response的输出流,然后他睡觉去了。这时候第二个线程又开始执行service,那么得到了一个新的用户名为"b",注意前面说过了这是临时变量,所以前面那个用户名没有被覆盖,还是"a",然后又将output引用指向了新的实例,因为这个引用是同一个,所以前面的引用就被覆盖了,然后他也睡觉去了。5s后第一个线程睡醒了,他向output里写东西,但是这时output已经不是与第一个浏览器的连接中的输出流,而是写到第二个浏览器里去了,当然他写的还是"a"。紧接着第二个线程也睡醒了,他将"b"也写到这个浏览器里去了。
结果是第一个浏览器啥也没得到,而第二个浏览器却得到了俩。
说到这里还有一个问题没有解决,为什么我第一次测试tomcat只启动了一个线程,而第二次却启动了两个呢?很奇怪,所以我又把代码后撤回去,回到第一个demo,又测试了两个页面,但这次测试的两个地址是第二次测试的地址,就是带上两个不同的参数。OK,这次跟奇怪了,输出结果不写了,只说我发现这次奇迹般的开了两个线程!
到这是我突然明白了,tomcat是这样决定,至少在本次测试的情况下是这样决定何时开启新的线程的:相同的URL就还用原来的线程,不管忙不忙;URL不同就新开一个线程。因为tomcat任务这个请求的URL不同那么就是带有不同的状态(我是这么理解的)。然后又测试了一下两组地址,更验证了以上的推理。
http://localhost:8080/test-servlet/DemoServlet?username=a和http://localhost:8080/test-servlet/DemoServlet?username=a
>>>同一个线程
http://localhost:8080/test-servlet/DemoServlet?username=a和http://localhost:8080/test-servlet/DemoServlet?username=a&userid=c
>>>不同线程
总结一下:
1. tomcat根据是否是同一URL来判断是否开启一个新的线程。
2. 不同请求,如果是同一线程,Servlet实例是同一个
3. 不同请求,如果是不同线程,Servlet实例是也同一个
但以上次总结仅限于我测试的情况,很可能在更加复杂的情况下tomcat会有更加复杂的处理和变化。
比如在并发量很大的时候以上的第1条可能就不成立了,可能还要考虑到队列的长度等问题。又比如当考虑了session的情况下第2和3条也许也不成立了。
这些问题以后再详细研究,最好是能跟一下tomcat源代码,才能最终弄清楚。
最后,有错误请大家指出,没有错误请大家补充。
本人新博客:tuoxie.me
分享到:
相关推荐
管中窥豹破解版2.6 管中窥豹 ~~~
"管中窥豹"是一款备受推崇的扫描工具,其最新版本v2.71在功能和性能上都得到了显著提升,使得用户能够更加高效地进行数据扫描和分析。这款软件以其出色的性能和易用性,相比之前的版本,用户体验有了显著的提升,...
管中窥豹2.6破解版本 咔咔破解版 渗透利器
【管中窥豹1.41版-注入工具】是一个在IT安全领域中常见的软件工具,主要用于检测和分析系统中的漏洞或潜在的安全风险。"注入工具"这一标签表明该程序的核心功能是执行数据库注入测试或者代码注入测试。这类工具通常...
网站检测入侵管中窥豹破解版```````
何登成在其演讲中深入探讨了MySQL(InnoDB)死锁问题,尤其对于死锁的分析提出了独到的见解和方法。以下是对何登成演讲内容的知识点详细梳理: ### 为什么选择“死锁” 何登成首先分享了他选择探讨死锁话题的原因,...
管中窥豹——MySQL(InnoDB)死锁分析之道 阿里巴巴高级数据库专家
管中窥豹LiQiDiS_2.71a33.7z.7z
报告中强调了氢氧化锂在稀有金属行业中战略地位的重要性,尤其对于头部资源企业。报告分析了雅宝、Pilbara和SQM等关键企业在氢氧化锂生产上的布局,揭示了氢氧化锂加工中的技术壁垒和长期的市场前景。 报告指出,...
最新注入工具,已部分破解,但会出现弹出窗口,没有关系
InstructGPT与Instruction Tuning: 管中窥豹ChatGPT - 知乎 InstructGPT是OpenAI开发的一种语言模型,旨在遵循用户意图,减少模型偏见。通过Instruction Tuning,InstructGPT模型可以更好地遵循用户指令,减少不...
医药行业政策系列研究(一):管中窥豹,收入结构看医改.pptx
报告标题:“20210824-天风证券-稀有金属行业:海外中报季后的管中窥豹,氢氧化锂,还是氢氧化锂”揭示了在全球稀有金属行业中,氢氧化锂作为关键材料的重要性。这篇报告可能是对2021年第二季度海外稀有金属公司业绩...
全球轮胎半年报管中窥豹” 本报告主要探讨了全球轮胎行业的增长态势,特别是在2019年上半年的表现。通过对全球前22大轮胎企业的半年报数据进行分析,揭示了行业发展趋势和潜在的增长驱动因素。 1. **全球销售增速*...
商业贸易行业:管中窥豹,个护家护赛道下沉市场洞察(2021)(13页).pdf
商业贸易行业专题研究:管中窥豹,个护家护赛道下沉市场洞察-20210219-天风证券-13页.pdf
全球产业公司系列宏观篇:管中窥豹,从各国上市龙头看产业竞争力-0218-国联证券-56页.pdf
电力设备与新能源行业深度报告:管中窥豹:从日韩锂电隔膜企业发展看我国锂电隔膜行业的现在和未来.pdf
嗯,首先说明,这是个很小的需求(毕业半年多接触的多是一些小需求),按理没什么好写的。但是之前写过一篇做用户体验设计的文章后,慢慢有了一些心得,有时候一些小的需求也会有灵光一现的时候,管中窥豹,觉得之前...