Servlet是web体系里面最重要的部分,下面罗列几道常见的面试题,小伙伴们一定要好好记住哈。
1.Servlet是单例的吗,如何证明?
Servlet一般都是单例的,并且是多线程的。如何证明Servlet是单例模式呢?很简单,重写Servlet的init方法,或者添加一个构造方法。然后,在web.xml中配置。如:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
<servlet>
<servlet-name>MyServlet</servlet-name>
<servlet-class>web.MyServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>MyServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>
然后是MyServlet
public class MyServlet extends HttpServlet{
public MyServlet(){
System.out.println("MyServlet构造函数调用了");
}
@Override
public void init() throws ServletException {
System.out.println("MyServlet初始化");
}
}
启动Tomcat,不管你访问多少次这个Servlet,init方法和构造器都只会执行1次。
2.如何让Servlet变成多例
方法1.实现 SingleThreadModel 接口(不推荐,官方已经将这个接口废弃)
public class MyServlet extends HttpServlet implements SingleThreadModel{
public MyServlet(){
System.out.println("MyServlet构造函数调用了");
}
@Override
public void init() throws ServletException {
System.out.println("MyServlet初始化");
}
}
SingleThreadModel
的意思是“单线程模式”,如果servlet实现了该接口,会确保不会有两个线程同时执行servlet的service方法。
servlet容器通过同步化访问servlet的单实例来保证,也可以通过维持servlet的实例池,对于新的请求会分配给一个空闲的servlet。源码中,最多会生成20个实例。
方法2. 在web.xml中多配置一个Servlet
哪怕是同一个Servlet,你在web.xml中配置几个,就会有几个实例。
3.你能证明Servlet线程不安全吗?
Servlet默认是线程不安全的!
Servlet体系结构是建立在Java多线程机制之上的,它的生命周期是由Web容器负责的。
当客户端第一次请求某个Servlet时,Servlet容器将会根据web.xml配置文件实例化这个Servlet类。
当有新的客户端请求该Servlet时,一般不会再实例化该Servlet类,也就是有多个线程在使用这个实例。
Servlet容器会自动使用线程池等技术来支持系统的运行。
当两个或多个线程同时访问同一个Servlet时,可能会发生多个线程同时访问同一资源的情况,数据可能会变得不一致。
所以在用Servlet构建的Web应用时如果不注意线程安全的问题,会使所写的Servlet程序有难以发现的错误。
下面举一个例子来说明,为什么Servlet是线程不安全的。
public class MyServlet extends HttpServlet{
String message;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
message = req.getParameter("message");
PrintWriter out = resp.getWriter();
//故意延时5秒钟,使得下一次请求过来的时候,message的值还没有返回就被覆盖了
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
out.write(message);
out.flush();
out.close();
}
}
打开两个浏览器,分别访问:
http://localhost:8080/web/hello?message=jack
http://localhost:8080/web/hello?message=rose
因为有5秒的延时,所以可能就会出现第一个Servlet还没返回呢,第二个Servlet就进来了。于是,把message的值给冲掉了。如下图
石锤了,Servlet是线程不安全的。
4.你怎么设计一个线程安全的Servlet?
1.最直接的办法,就是用上面的SingleThreadModel接口
既然单例会有共享实例变量导致线程不安全的问题,那就改成多例的呗。
但是,这个接口都已经被官方废弃了,这就说明官方也不推荐这么做。原因很简单,那就是这样一来会有很多个实例,性能的代价太大了。
-
用同步锁
这也是非常容易想到的办法,把当前对象锁起来,不返回不给其他用户插入(怎么有点怪怪的?)
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
synchronized(this){
message = req.getParameter("message");
PrintWriter out = resp.getWriter();
//故意延时5秒钟,使得下一次请求过来的时候,message的值还没有释放
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
out.write(message);
out.flush();
out.close();
}
}
这样的代价就是等待时间更长了,参考火车上的的卫生间,这就是同步锁。
-
尽量别用实例变量,用局部变量代替