测试Servlet是否线程安全
测试代码
package com.servlet.study; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class ThreadSafeTestServlet extends HttpServlet{ /** * */ private static final long serialVersionUID = -5678315968996207090L; private String name; @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html"); resp.setCharacterEncoding("utf-8"); // TODO Auto-generated method stub System.out.println(this+":"+Thread.currentThread()); name = req.getParameter("user"); try { Thread.sleep(2000); } catch (InterruptedException e1) { // TODO Auto-generated catch block } resp.getWriter().println("用户名:"+name);; System.out.println("Counter = "+name); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // TODO Auto-generated method stub doPost(req, resp); } }
页面请求:
请求1:http://localhost:8080/TESTWeb/thread?user=lisi
请求2:http://localhost:8080/TESTWeb/thread?user=wangw
输出结果:
com.servlet.study.ThreadSafeTestServlet@5dcc783c:Thread[http-nio-8080-exec-27,5,main]
com.servlet.study.ThreadSafeTestServlet@5dcc783c:Thread[http-nio-8080-exec-24,5,main]
Counter = wangw
Counter = wangw
请求1的输出,被请求2的name改写了,用户lisi的页面显示wangw的信息。
结论:
1、Servlet对象是一个无状态的单例对象(Singleton),因为请求多次,打印出来的hashcode值都相同。
2、Servlet在不同的线程池中运行,如上的Thread[http-nio-8080-exec-27和 24 区分出不同的线程执行了同一段Servlet逻辑代码。
3、name变量在不同的线程中共享,且它的值被不同的线程修改。输出时,已被其它线程修改后的值。
也就是说,其它线程会篡改当前线程中实例变量的值,针对这些对象的访问不是线程安全的。
解决方式一:
使用局部变量,代替全局变量。
package com.servlet.study; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class ThreadSafeTestServlet extends HttpServlet{ /** * */ private static final long serialVersionUID = -5678315968996207090L; @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String name; resp.setContentType("text/html"); resp.setCharacterEncoding("utf-8"); // TODO Auto-generated method stub System.out.println(this+":"+Thread.currentThread()); name = req.getParameter("user"); try { Thread.sleep(2000); } catch (InterruptedException e1) { // TODO Auto-generated catch block } resp.getWriter().println("用户名:"+name);; System.out.println("Counter = "+name); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // TODO Auto-generated method stub doPost(req, resp); } }
输出:
com.servlet.study.ThreadSafeTestServlet@53a2686d:Thread[http-nio-8080-exec-44,5,main]
com.servlet.study.ThreadSafeTestServlet@53a2686d:Thread[http-nio-8080-exec-39,5,main]
Counter = wangw
Counter = lisi
解决方式二:
使用synchorized关键字,保证一次只有一个线程访问保护的区域块。
private String name; @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html"); resp.setCharacterEncoding("utf-8"); // TODO Auto-generated method stub System.out.println(this+":"+Thread.currentThread()); synchronized(this){ name = req.getParameter("user"); try { Thread.sleep(2000); } catch (InterruptedException e1) { // TODO Auto-generated catch block } resp.getWriter().println("用户名:"+name);; System.out.println("Counter = "+name); } }
页面请求:
http://localhost:8080/TESTWeb/thread?user=lisi
http://localhost:8080/TESTWeb/thread?user=wangw
输出结果:
信息: Reloading Context with name [/TESTWeb] is completed
com.servlet.study.ThreadSafeTestServlet@3fadc5ba:Thread[http-nio-8080-exec-8,5,main]
com.servlet.study.ThreadSafeTestServlet@3fadc5ba:Thread[http-nio-8080-exec-4,5,main]
Counter = wangw
Counter = lisi