`
tuoxie007
  • 浏览: 162739 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

管中窥豹,结合多线程,对于tomcat中servlet加载的一点试探

    博客分类:
  • Java
阅读更多

对于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
1
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics