Servlet是单例模式,自始至终创建一个对象,Tomcat是Servlet的容器,并且tomcat内部维护了一个线程池,Tomcat运用了IO多路复用技术实现,每个请求都会创建一个线程来进行处理。当多个请求同时访问同一个servlet对象时候,会创建多个线程对同一个对象进行操作,所以会产生线程安全问题。
方案1: 实现SingleThreadModel接口
我们可以让Servlet类实现SingleThreadModel接口,每个线程都会创建servlet实例,避免了多线程使用通过Servlet实例的请求,但是使用这种方式会导致对客户端的请求响应效率变低,增加了服务器因频繁创建和销毁Servlet实例的开销,因此此种方式不建议使用,已经过时。
方案2:使用synchronized同步锁
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
synchronized (this) {
// 业务代码
}
}
建议:在Servlet实例中尽量不使用成员变量
如果将变量定义为成员变量,则这个变量在多个线程中是共享的,就有可能因为多个线程同时修改这个变量导致并发问题,因此我们可以将变量定义在处理业务的doXX方法中,定义为局部变量之后,每个线程都有属于自己的局部变量。
Servlet实例的生命周期
当客户端的请求到达Tomcat,Tomcat会创建一个线程来接收、处理、响应客户端请求,客户端在请求某个Servlet类时,线程需要通过这个Servlet类的实例来调用service方法、调用doGet/doPost..方法来处理响应请求,这个Servlet类的实例是何时创建、何时销毁的呢?Servlet实例的生命周期指的是一个Servlet类的实例从创建到销毁的过程。
Servlet类是单实例多线程的,一个Servlet类自始至终只会创建一个对象;
如果当前Servlet类没有配置
<load-on-startup>1</load-on-startup>:
当客户端第一次请求Servlet时,创建当前Servlet类的实例,然后使用这个实例调用service(ServletRequest, ServletResponse)方法——》service(HttpServletRequest, HttpServletResponse)方法——》doGet/doPost处理客户端请求;当客户端请求再次到达时将不会重新创建Servlet实例,直接使用第一次创建的实例调用方法进行响应;
如果当前Servlet类配置了
<load-on-startup>1</load-on-startup>:
当服务器启动时就会创建Servlet类的实例,无论客户端第一次请求这个Servlet类,还是再次请求都不会创建Servlet类实例,直接使用服务器启动时创建的Servlet实例来接收、处理、响应客户端请求;
当服务器关闭时,Serlvet类的实例会被销毁。
注意:配置
<load-on-startup>1</load-on-startup>
和不配置的区别是:配置了那么服务器在启动时就会把我们自己定义的Servlet对象创建出来。否则就要等到具体发起请求时才会创建对象。load-on-startup
里的参数表示对象被创建的出来的顺序。配置load-on-startup的两种方式:
- xml配置
<servlet>
<servlet-name>TestServlet</servlet-name>
<servlet-class>com.qfedu.servlets.TestServlet</servlet-class>
<!--如果有多个Servlet都配置了load-on-startup,里面的数字就是在服务器中创建实例的顺序-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>TestServlet</servlet-name>
<url-pattern>/test</url-pattern>
</servlet-mapping>
- 注解配置
@WebServlet(value = "/test",loadOnStartup = 1)