Servlet的线程安全问题
2015-10-28 11:12
681 查看
我们知道当多个客户端并发访问同一个Servlet时,web服务器会为每一个客户端的访问请求创建一个线程,并在这个线程上调用Servlet的service方法,因此service方法内如果访问了同一个资源的话,就有可能引发线程安全问题,看下面代码:
如果是下面这种情况就会存在线程安全问题了
首先我们想到的是加synchronized,并发访问i时就不存在线程安全问题了。为什么加了synchronized后就没有线程安全问题了呢?假如现在有一个线程访问Servlet对象,那么它就先拿到了Servlet对象的那把锁等到它执行完之后才会把锁还给Servlet对象,由于是它先拿到了Servlet对象的那把锁,所以当有别的线程来访问这个Servlet对象时,由于锁已经被之前的线程拿走了,后面的线程只能排队等候了。这种做法虽然解决了线程安全问题,但是编写Servlet却万万不能用这种方式处理线程安全问题,假如有5000个人同时访问这个Servlet,那么这5000个人必须按先后顺序排队轮流访问。这样效率很低。
针对Servlet的线程安全问题,Sun公司是提供有解决方案的:让Servlet去实现一个SingleThreadModel接口,如果某个Servlet实现了SingleThreadModel接口,那么Servlet引擎将以单线程模式来调用其service方法。
对于实现了SingleThreadModel接口的Servlet,Servlet引擎仍然支持对该Servlet的多线程并发访问,其采用的方式是产生多个Servlet实例对象,并发的每个线程分别调用一个独立的Servlet实例对象。
实现SingleThreadModel接口并不能真正解决Servlet的线程安全问题,因为Servlet引擎会创建多个Servlet实例对象,而真正意义上解决多线程安全问题是指一个Servlet实例对象被多个线程同时调用的问题。事实上,在Servlet API 2.4中,已经将SingleThreadModel标记为Deprecated(过时的)。
ThreadLocal实现
执行结果:
1 服务器对每个Servlet只创建一个实例。flag不停增加
2 Session范围内的ThreadLocal中对象唯一。不同的请求,Object的hashCode相同。
3 不同的Session共享ThreadLocal,但内部对象不同
package com.oec; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class Servlet extends HttpServlet { /** * Constructor of the object. */ public Servlet() { super(); } /** * Destruction of the servlet. <br> */ public void destroy() { super.destroy(); // Just puts "destroy" string in log // Put your code here } /** * The doGet method of the servlet. <br> * * This method is called when a form has its tag value method equals to get. * * @param request the request send by the client to the server * @param response the response send by the server to the client * @throws ServletException if an error occurred * @throws IOException if an error occurred */ public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); int i =1; //定义一个局部变量i i++; //i增加 PrintWriter out = response.getWriter(); out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">"); out.println("<HTML>"); out.println(" <HEAD><TITLE>A Servlet</TITLE></HEAD>"); out.println(" <BODY>"); out.print(" This is "); out.print(i); //输出i的值 out.println(", using the GET method"); out.println(" </BODY>"); out.println("</HTML>"); out.flush(); out.close(); /** * 在这段代码中,多线程并发访问时,不会存在线程安全问题,因为i是doGet方法的局部变量。 * 当有多个线程并发访问doGet方法时,每一个线程里面都有自己的i变量,每一个线程都操作自己的i所以不存在 */ } /** * The doPost method of the servlet. <br> * * This method is called when a form has its tag value method equals to post. * * @param request the request send by the client to the server * @param response the response send by the server to the client * @throws ServletException if an error occurred * @throws IOException if an error occurred */ public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); out .println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">"); out.println("<HTML>"); out.println(" <HEAD><TITLE>A Servlet</TITLE></HEAD>"); out.println(" <BODY>"); out.print(" This is "); out.print(this.getClass()); out.println(", using the POST method"); out.println(" </BODY>"); out.println("</HTML>"); out.flush(); out.close(); } /** * Initialization of the servlet. <br> * * @throws ServletException if an error occurs */ public void init() throws ServletException { // Put your code here } }
如果是下面这种情况就会存在线程安全问题了
package com.oec; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class Servlet extends HttpServlet { int i =1; //定义成全局变量 /** * Constructor of the object. */ public Servlet() { super(); } /** * Destruction of the servlet. <br> */ public void destroy() { super.destroy(); // Just puts "destroy" string in log // Put your code here } /** * The doGet method of the servlet. <br> * * This method is called when a form has its tag value method equals to get. * * @param request the request send by the client to the server * @param response the response send by the server to the client * @throws ServletException if an error occurred * @throws IOException if an error occurred */ public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); i++; //i增加 PrintWriter out = response.getWriter(); out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">"); out.println("<HTML>"); out.println(" <HEAD><TITLE>A Servlet</TITLE></HEAD>"); out.println(" <BODY>"); out.print(" This is "); out.print(i); //输出i的值 out.println(", using the GET method"); out.println(" </BODY>"); out.println("</HTML>"); out.flush(); out.close(); /** * 把i定义成全局变量,当多个线程并发访问变量i时,就会存在线程安全问题了, * 同时开启两个浏览器模拟并发访问同一个Servlet,本来正常来说,第一个浏览器应该看到2, * 而第二个浏览器应该看到3的,结果两个浏览器都看到了3 */ } /** * The doPost method of the servlet. <br> * * This method is called when a form has its tag value method equals to post. * * @param request the request send by the client to the server * @param response the response send by the server to the client * @throws ServletException if an error occurred * @throws IOException if an error occurred */ public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); out .println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">"); out.println("<HTML>"); out.println(" <HEAD><TITLE>A Servlet</TITLE></HEAD>"); out.println(" <BODY>"); out.print(" This is "); out.print(this.getClass()); out.println(", using the POST method"); out.println(" </BODY>"); out.println("</HTML>"); out.flush(); out.close(); } /** * Initialization of the servlet. <br> * * @throws ServletException if an error occurs */ public void init() throws ServletException { // Put your code here } }线程安全问题只存在多个线程并发操作同一个资源的情况下,所以在编写Servlet的时候,如果并发访问某一个资源(变量,集合等),就会存在线程安全问题,那么该如何解决这个问题。
首先我们想到的是加synchronized,并发访问i时就不存在线程安全问题了。为什么加了synchronized后就没有线程安全问题了呢?假如现在有一个线程访问Servlet对象,那么它就先拿到了Servlet对象的那把锁等到它执行完之后才会把锁还给Servlet对象,由于是它先拿到了Servlet对象的那把锁,所以当有别的线程来访问这个Servlet对象时,由于锁已经被之前的线程拿走了,后面的线程只能排队等候了。这种做法虽然解决了线程安全问题,但是编写Servlet却万万不能用这种方式处理线程安全问题,假如有5000个人同时访问这个Servlet,那么这5000个人必须按先后顺序排队轮流访问。这样效率很低。
针对Servlet的线程安全问题,Sun公司是提供有解决方案的:让Servlet去实现一个SingleThreadModel接口,如果某个Servlet实现了SingleThreadModel接口,那么Servlet引擎将以单线程模式来调用其service方法。
package com.oec; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.SingleThreadModel; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class Servlet extends HttpServlet implements SingleThreadModel{ int i =1; //定义成全局变量 /** * Constructor of the object. */ public Servlet() { super(); } /** * Destruction of the servlet. <br> */ public void destroy() { super.destroy(); // Just puts "destroy" string in log // Put your code here } /** * The doGet method of the servlet. <br> * * This method is called when a form has its tag value method equals to get. * * @param request the request send by the client to the server * @param response the response send by the server to the client * @throws ServletException if an error occurred * @throws IOException if an error occurred */ public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); i++; //i增加 PrintWriter out = response.getWriter(); out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">"); out.println("<HTML>"); out.println(" <HEAD><TITLE>A Servlet</TITLE></HEAD>"); out.println(" <BODY>"); out.print(" This is "); out.print(i); //输出i的值 out.println(", using the GET method"); out.println(" </BODY>"); out.println("</HTML>"); out.flush(); out.close(); /** * 把i定义成全局变量,当多个线程并发访问变量i时,就会存在线程安全问题了, * 同时开启两个浏览器模拟并发访问同一个Servlet,本来正常来说,第一个浏览器应该看到2, * 而第二个浏览器应该看到3的,结果两个浏览器都看到了3 */ } /** * The doPost method of the servlet. <br> * * This method is called when a form has its tag value method equals to post. * * @param request the request send by the client to the server * @param response the response send by the server to the client * @throws ServletException if an error occurred * @throws IOException if an error occurred */ public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); out .println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">"); out.println("<HTML>"); out.println(" <HEAD><TITLE>A Servlet</TITLE></HEAD>"); out.println(" <BODY>"); out.print(" This is "); out.print(this.getClass()); out.println(", using the POST method"); out.println(" </BODY>"); out.println("</HTML>"); out.flush(); out.close(); } /** * Initialization of the servlet. <br> * * @throws ServletException if an error occurs */ public void init() throws ServletException { // Put your code here } }让Servlet实现了SingleThreadModel接口,只要在Servlet类的定义中增加实现SingleThreadModel接口的声明即可。
对于实现了SingleThreadModel接口的Servlet,Servlet引擎仍然支持对该Servlet的多线程并发访问,其采用的方式是产生多个Servlet实例对象,并发的每个线程分别调用一个独立的Servlet实例对象。
实现SingleThreadModel接口并不能真正解决Servlet的线程安全问题,因为Servlet引擎会创建多个Servlet实例对象,而真正意义上解决多线程安全问题是指一个Servlet实例对象被多个线程同时调用的问题。事实上,在Servlet API 2.4中,已经将SingleThreadModel标记为Deprecated(过时的)。
ThreadLocal实现
package com.oec; import java.io.IOException; import java.io.PrintWriter; import java.net.InetSocketAddress; import java.util.concurrent.Executor; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.sun.net.httpserver.HttpContext; import com.sun.net.httpserver.HttpHandler; public class TestThreadServlet extends HttpServlet{ private static ThreadLocal thread = new ThreadLocal(); private int flag = 0; public void doGet( HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { flag++; String str = "This is the first String." + new Object(); if (thread.get() == null) thread.set(str); PrintWriter out = response.getWriter(); out.println("<p>"); out.println("<BR>flag : " + flag); out.println("<BR>sessionid : " + request.getSession().getId()); out.println("<BR>servlet : " + this.toString()); out.println("<BR>thread : " + thread.get()); out.println("</p>"); } }
执行结果:
Session 1: flag : 2 sessionid : amGeaiVwKvL9 servlet : test.other.TestThreadServlet@5f2db0 thread : This is the first String.java.lang.Object@1ad6b4b Session 1: flag : 3 sessionid : aR3GkcUQoXT- servlet : test.other.TestThreadServlet@5f2db0 thread : This is the first String.java.lang.Object@6214f5
1 服务器对每个Servlet只创建一个实例。flag不停增加
2 Session范围内的ThreadLocal中对象唯一。不同的请求,Object的hashCode相同。
3 不同的Session共享ThreadLocal,但内部对象不同
相关文章推荐
- 使用Session防止表单重复提交
- 解决 safari window.open 无法实现的问题
- Cracking the Coding Interview 之测试
- git rebase简介
- CE3和UE3在多线程渲染方面的简单对比
- JQuery上传插件Uploadify + Jcrop图像裁剪的几个常用小技巧
- c#网页方式获取新浪微博的微博数据
- zabbix数据库表结构简单解析
- SqlCommandBuilder类批量更新excel或者CSV数据的方法
- C++11学习笔记3---auto&decltype
- 集合类声明容器的元素 ----Java编程之范型
- iOS实现一个颜色渐变的弧形进度条
- 阻塞 非阻塞 select epoll
- Xcode 上传APP的时候 出现问题 XCode ERROR ITMS-90049 This bundle is invalid The bundle identifier contains di
- android集成新浪官网SDK精简版
- html网页自动跳转的三大技巧
- 【HTK]htkbook学习笔记
- 【转载】xtrabackup原理及实施
- VC++ ADO 连接 mysql
- jQuery学习之旅 Item8 DOM事件操作