面试题1. servlet的执行流程?(从一个请求过来开始说)
(1)Servlet的加载和实例
- 因为Servlet是运行在Servlet容器(其实就是Web应用的Context容器)中,首先当Servlet容器启动的时候,容器会去进行检测如果是否需要创建一个Servlet对象去响应请求的时候,这是时就会去进行通过Java的反射API来创建 Servlet实例,调用的是Servlet的默认构造方法(即不带参数的构造方法)
- 这一步主要是将 Servlet 包装成 Context 容器中的 StandardWrapper,目的是因为StandardWrapper 是 Tomcat 容器中的一部分,而Servlet为了独立的Web开发标准,不应该耦合到Tomcat容器中。
(2)Servlet的初始化
- 初始化其实就是在上一步中包装成的 StandardWrapper对象中来调用initServlet 方法,同时把包装了 StandardWrapper 对象的 StandardWrapperFacade 作为 ServletConfig 传给 Servlet,这个ServletConfig在Tomcat容器中的位置如下图:
- 其实他这么做的目的其实就是不想让Servlet拿到他不关心的数据,所以传给 Servlet 的是 StandardWrapperFacade 对象,这个类能够保证从 StandardWrapper 中拿到 ServletConfig 所规定的数据,而又不把 ServletConfig 不关心的数据暴露给 Servlet。这里使用到了门面模式的设计模式。
- 所以到这里Servlet实例可以使用容器为它准备的ServletConfig对象从Web应用程序的配置信息(在web.xml中配置)中获取初始化的参数信息
(3)Servlet的请求处理
- 到这里其实我们需要使用到的Servlet就已经准备好了,然后当用户的请求过来的时候,就会根据我们配置的servlet-mapping和servlet-name的一个映射关系就能找到我们需要处理用户请求的Servlet。
- 然后Servlet实例会通过ServletRequest来获取用户请求带过来的数据和信息,来去执行Servlet的service()方法,然后会在service方法中进行对不同的提交方式进行一个分发。
(4)Servlet的销毁
- 当容器检测到一个Servlet实例应该从服务中被移除的时候,容器就会调用实例的destroy()方法,以便让该实例可以释放它所使用的资源, 保存数据到持久存储设备中。当需要释放内存或者容器关闭时,容器就会调用Servlet实例的destroy()方法。在destroy()方法调用之 后,容器会释放这个Servlet实例
面试题2. 什么时候进行初始化servlet的请求?
首先需要进行分几种情况的时候才会去进行初始化Servlet的请求:
- 当我们的Servlet容器开始启动个的时候,读取web.xml配置文件中的信息,构造指定的Servlet对象,创建ServletConfig对象,同时将ServletConfig对象作为参数来调用Servlet对象的init方法。
- 当Servlet容器启动后,客户端第一次向Servlet发出请求的时候,Servlet会先去容器中进行获取,如果获取不到的话,就会去进行创建一个新的Servlet实例对象。
- Servlet的类文件被更新后,重新创建ServletServlet容器在启动时自动创建Servlet,这是由在web.xml文件中为Servlet设置的属性决定的,并且每一个Servlet对象都是以一个单例存在。
面试题3. 如果在servlet中定一个全局变量,那么这个全局变量是线程安全的吗?
- 这个全局变量不是线程安全的,因为Servlet其实是以单例多线程方式运行的,如果有多个线程想要去访问同一个Servlet中的同一个全局变量的时候,此时就会导致线程不安全,
- 因为Servlet对象是放在JVM的堆中的,如果全局变量引用的是一个对象的话,那么这个对象其实也只是会有一份的,如果有多个线程过来去访问的话,那么要是不加任何锁机制的话,那么肯定是会出现线程安全问题的
- 但是如果要是把一个全局变量定义为一个方法中的局部变量的话,那么就不会出现线程安全的问题的,因为每一个方法在调用的时候都是会放在每一个线程中的栈贞中的,这样的话,哪个线程去调用这个方法,就会在哪个线程中开辟一块方法栈来去保存一些局部变量表的信息,这样就能保证每个线程都在自己家里利用自己的空间,不会影响别人的家里的空间(这里的家就是指线程的存储空间)
- 最终只要这个方法栈执行完毕之后,那么这个线程中创建的栈贞也会随之销毁,所以每个线程都会有自己的局部变量,不会共享。但是如果是放在Servlet中的全局对象的话,那么这个局部对象就只会和Servlet对象一起共生死,就会导致多个线程共享一个资源。肯定会出现线程安全问题。
面试题4. 那如果我想让他是线程安全的话,我应该怎么去改呢?(如果多个线程访问的是同一个成员变量,会共享成员变量,而在访问局部变量的时候,每个线程都有自己的变量,不会共享)
面试题5. 使用servlet来进行看有多少人来进行请求,请求用的时间多久?
先了解Servlet如何处理多个请求访问的运行原理:
- 首先你得知道servlet容器(其实就是专门管理servlet对象的一个容器)是单实例多线程的方式来进行处理请求的,什么意思呢?其实就是当web服务器启动的时候(或者当客户端发送请求到服务器的时候)servlet就被加载并且被实例化
- 然后容器的初始化Servlet其实就是加载配置文件(比如在Tomcat中的server.xml文件中的一些配置,例如可以配置设置线程池中线程数目,初始化线程池通过web.xml,初始化每个参数值等等。)
- 当用户的请求到达时,Servlet容器会通过调度线程(Dispatchaer Thread),来进行调度它管理下线程池中等待执行的线程(Worker Thread)给请求者
- 然后线程会去执行Servlet的service()方法,然后用完之后就会把该线程再放入到线程池中,供下一个过来的请求好分配线程来进行使用
- 注意:避免使用实例变量(成员变量),因为如果存在成员变量,可能发生多线程同时访问该资源时,都来操作它,照成数据的不一致,因此产生线程安全问题
通过上面也能知道:Servlet容器并不关心到达的Servlet请求访问的是否是同一个Servlet还是另一个Servlet,直接分配给它一个新的线程;如果是同一个Servlet的多个请求,那么Servlet的service方法将在多线程中并发的执行,因此不建议在servlet中定义成员变量
那么怎么解决servlet的线程安全问题呢?
- 第一点是不要在Servlet中定义全局变量,最好使用局部变量,也是最推荐使用的一种
- 最好不要使用实例变量(成员变量)
- 然后就是可以使用Synchronize 关键字进行加锁,但是这个效率太低了,如果是在高并发情况先,线程获取这个加锁的servlet对象的话,会进行派对阻塞,影响性能。
- 可以实现SingleThreadModel 这个接口,但是这个会有很大的系统开销,在servlet2.4之后就被废弃了
那么如何通过servlet统计在线人数和网站访问次数呢?
- 要是想要统计网站的访问次数就需要利用到ServletContext来进行实现,因为ServletContext对象是可以监听对象的创建、销毁、添加属性、删除属性、属性值的改变等,具有全局属性特点
- 我们就应该在服务器关闭前将当前的访问量存放到文件里面,以便下一次重启服务器后,可以继续使用。在ServletContext上面创建监听器,监听上下文对象的销毁和创建,并同时在创建上下文的时候从文件读取历史数据,在上下文销毁的时候将当前访问量写入到文件保存起来
- 以后每当创建一个会话(Session)的时候,就将当前的计数值加一。在线人数的统计是利用在创建会话的时候,将在线人数之加一,在会话对象销毁的时候,将在线人数值减一。