对于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 管中窥豹 ~~~
管中窥豹2.6破解版本 咔咔破解版 渗透利器
管中窥豹v2.71最新版 非常好用的一款扫描工具,比之前的版本更好用,速度更快
网站检测入侵管中窥豹破解版```````
看到有人跪求这个,我就发出来了。后门未测,大家谨慎使用.
2015 Oracle 技术嘉年华(OTN)分会场11何登成 - 管中窥豹——MySQL(InnoDB)死锁分析之道
20210824-天风证券-稀有金属行业:海外中报季后的管中窥豹,氢氧化锂,还是氢氧化锂.pdf
管中窥豹——MySQL(InnoDB)死锁分析之道 阿里巴巴高级数据库专家
管中窥豹LiQiDiS_2.71a33.7z.7z
InstructGPT与Instruction Tuning_ 管中窥豹ChatGPT - 知乎.pdf
最新注入工具,已部分破解,但会出现弹出窗口,没有关系
医药行业政策系列研究(一):管中窥豹,收入结构看医改.pptx
稀有金属行业研究周报:海外中报季后的管中窥豹:氢氧化锂,还是氢氧化锂.pdf
橡胶行业专题报告:轮胎行业增长点在哪里?全球轮胎半年报管中窥豹.pdf
商业贸易行业:管中窥豹,个护家护赛道下沉市场洞察(2021)(13页).pdf
20210824-天风证券-稀有金属行业:海外中报季后的管中窥豹,氢氧化锂,还是氢氧化锂.zip
商业贸易行业专题研究:管中窥豹,个护家护赛道下沉市场洞察-20210219-天风证券-13页.pdf
全球产业公司系列宏观篇:管中窥豹,从各国上市龙头看产业竞争力-0218-国联证券-56页.pdf
网络安全审查制度之“管中窥豹”——评《网络产品和服务安全审查办法》(征求意见稿).pdf