Servlet的线程安全问题
Servlet是一个供Servlet引擎(Web服务器)调用的Java类,它不能独立运行,它的运行完全由Servlet引擎来控制和调度。针对客户端的多次Servlet请求,通常情况下,服务器只创建一个Servlet实例对象,也就是说Servlet实例对象一旦创建,它就会驻留在内存中,为后续的其他请求服务器,直至Web容器退出(关闭浏览器),Servlet实例对象才会销毁。
注意:
一个Servlet被客户端发送的第一请求激活,然后将继续运行于后台,等待以后的请求。每个请求将生成一个新的线程,而不是一个完整的进程。
在Servlet的整个生命周期内,Servlet的init方法和destroy方法都只被调用一次。而当客户端的每次访问该Servlet,Servlet引擎就会调用一次service()方法。所以每次request和response都是全新的封装对象。(即每个用户调用Servlet时,request和response都不一样的)。
当Servlet第一次被访问后,就被加载到内存中,以后该实例对各个请求服务,所以就引发了单例问题。
举例:Servlet成员变量和局部变量的例子。
创建一个Web Project (demo(Web应用))
在demo下创建一个Servlet(demo1.java),代码如下:
package com.focus.Thread;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class demo1 extends HttpServlet {
//定义一个整型的成员变量。
int i = 0;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
//定义一个局部变量。
int j = 0;
i++;
j++;
out.println("成员变量的值 i = "+i+"局部变量的值 j = "+j);
}
}
部署:<servlet-mapping>
<servlet-name>demo1</servlet-name>
<url-pattern>/demo</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>demo1</servlet-name>
<servlet-class>com.focus.Thread.demo1</servlet-class>
</servlet>
效果如下:
由此可以看出,成员变量是共享的数据,因为Servlet是以多线程模式执行的,当有多个客户同时并发请求一个Servlet时,容器将启动多个线程调用相应的请求处理方法,此时,请求处理方法中的局部变量是安全的,但对于成员变量和共享数据是不安全的,因为这多个线程有可能同时都操作到这些数据,此时就需要同步处理。
线程安全问题。
举例:售票系统
demo1.java代码如下:
package com.focus.Thread;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class demo1 extends HttpServlet {
int ticket = 2;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
if(ticket>0){
System.out.println("恭喜您,您买到票了。");
//此处的意图是要模拟当访问数量很多的时候。休眠10秒。
try{
Thread.sleep(1000*10);
}catch(InterruptedException e){
e.printStackTrace();
}
ticket--;
}else{
out.println("不好意思,我们的票已售完,请下次早点。");
}
}
}
以下是当浏览器不断刷新时,服务器窗口所显示。
当过了10秒后。
处理方法:就是把代码加上同步机制。
public class demo1 extends HttpServlet {
int ticket = 2;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
synchronized(this){
if(ticket>0){
System.out.println("恭喜您,您买到票了。");
try{
Thread.sleep(1000*10);
}catch(InterruptedException e){
e.printStackTrace();
}
ticket--;
}else{
out.println("不好意思,我们的票已售完,请下次早点。");
}
}
}
总结:
(1)使用synchronized可同步操作同步变量和共享数据的代码,就可以防止可能出现的线程安全问题,但这也意味着线程需要排队处理。因此,要缩短使用的范围。
Synchronized(对象){
//同步代码
}
(2)尽量少使用成员变量和共享数据。如果一个变量不需要共享,则把该变量设为局部变量,就是直接在doGet()或者doPost()方法中定义,这样也就不会存在单例安全问题了。
ServletConfig接口
在初始化过程中,Servlet容器使用ServletConfig接口的对象作为参数来传递Servlet的配置信息。
方法如下:
getServletName(): 用于获取Servlet实例的名称。
getInitParameter(): 检索初始化参数的值,如果参数不存在,则返回null。
getServletContext(): 返回Servlet用来与其容器交互的ServletContext对象。
ServletConfig对象用于读取servlet的配置信息
举例说明:
创建一个Servlet(demo2.java)
在web.xml中添加标签如下:
<servlet>
<servlet-name>demo2</servlet-name>
<servlet-class>com.focus.Thread.demo2</servlet-class>
<!--下面是给Servlet配置信息,给配置的信息,只能有该Servlet读取-->
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</servlet>
demo2.java代码如下:
package com.focus.Thread;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class demo2 extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
//通过这种方式获得配置信息。
String encoding = this.getServletConfig().getInitParameter("encoding");
response.setCharacterEncoding(encoding);
PrintWriter out = response.getWriter();
out.println("编码方式为:"+encoding);
}
}
效果如下:
而上面的这种配置只能由创建的Servlet读取,当想要全部Servlet都可以使用的配置信息时,可采用以下的方法:
<!--下面配置的参数,可被所有Servlet读取-->
<context-param>
<param-name></param-name>
<param-value></param-value>
</context-param>
当配置的信息有很多,想要把配置信息都获取到,代码如下:
配置信息如下:
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
<init-param>
<param-name>name</param-name>
<param-value>focus</param-value>
</init-param>
<init-param>
<param-name>1</param-name>
<param-value>2</param-value>
</init-param>
demo2.java添加如下代码:
//获取配置的所有名字
Enumeration<String> names = this.getServletConfig().getInitParameterNames();
//循环判断还有没有下一个元素
while(names.hasMoreElements()){
String name = names.nextElement();
String value = this.getServletConfig().getInitParameter(name);
out.println("<br/>配置的名字:"+name+"配置的值:"+value);
}
效果如下: