当前位置: 首页 >服务端 > Servlet是单例的吗?

Servlet是单例的吗?

  如题,是吗?首先我们得搞清楚啥是单例。一聊起单例,条件反射的第一个想到的自然是单例模式。单例模式的定义:一个类有且仅有一个实例,并且自行实例化向整个系统提供。如果按照Java中单例的定义,那么当Servlet没有实现SingleThreadModel接口时,它确实是单例的。

  但如果往细处再进一步深究的话,又好像不是那么一回事了。还是先看单例模式,举个例子,Sigleton类是个单例,它为了保证实例的唯一性,坚决不给别人实例化它的机会,那么它会把构造器定义为私有的(private),这样其他人就没法new出它的实例了(但其实通过反射还是可以实例化的,这里不展开)。而Servlet本身是一个接口,我们一般用的是HttpServlet,它继承了GenericServlet,而GenericServlet实现了Servlet。虽然HttpServlet是抽象类,然而它却有自己的构造器,而且是公有的(public)。我们知道子类初始化实例时必然先调用父类的构造器,也就是如果我现在有一个DemoServlet,那么实例化它将执行HttpServlet的构造器。当然了,父亲的父亲GenericServlet的构造器也会加载,而且这位祖父的构造器也是公有的。如此看来,单例模式中的私有构造器与Servlet中的公有构造器明显匹配不上了。

  综上所述,我还是偏向于广义上的范畴,只要满足在整个系统中仅有一个实例,就认为它是单例。回到最先前的那句话:当Servlet没有实现SingleThreadModel接口时,它才是单例的。虽然SingleThreadModel被标记为过期的了,但仍可以用。如果实现该接口,那么每次请求相同的Servlet,将创建一个新的实例。说白了就跟CGI一样了,每次web请求都起一个进程来处理。

  Servlet本身是规范,它需要实现了这组规范的Servlet容器来提供web能力。一提到servlet容器,条件反射的第一个想到的自然是Tomcat。Tomcat才是去实例化Servlet的那个他。而Tomcat里执行实例化Servlet的类叫StandardWrapper,它有个loadServlet的方法:

/** * Load and initialize an instance of this servlet, if there is not already * at least one initialized instance.  This can be used, for example, to * load servlets that are marked in the deployment descriptor to be loaded * at server startup time. * @retu the loaded Servlet instance * @throws ServletException for a Servlet load error */public synchronized Servlet loadServlet() throws ServletException {// Nothing to do if we already have an instance or an instance poolif (!singleThreadModel && (instance != null))retu instance;PrintStream out = System.out;if (swallowOutput) {SystemLogHandler.startCapture();}Servlet servlet;try {long t1=System.currentTimeMillis();// Complain if no servlet class has been specifiedif (servletClass == null) {unavailable(null);throw new ServletException(sm.getString("standardWrapper.notClass", getName()));}InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();try {servlet = (Servlet) instanceManager.newInstance(servletClass);} catch (ClassCastException e) {unavailable(null);// Restore the context ClassLoaderthrow new ServletException(sm.getString("standardWrapper.notServlet", servletClass), e);} catch (Throwable e) {e = ExceptionUtils.unwrapInvocationTargetException(e);ExceptionUtils.handleThrowable(e);unavailable(null);// Added extra log statement for Bugzilla 36630:// https://bz.apache.org/bugzilla/show_bug.cgi?id=36630if(log.isDebugEnabled()) {log.debug(sm.getString("standardWrapper.instantiate", servletClass), e);}// Restore the context ClassLoaderthrow new ServletException(sm.getString("standardWrapper.instantiate", servletClass), e);}if (multipartConfigElement == null) {MultipartConfig annotation =servlet.getClass().getAnnotation(MultipartConfig.class);if (annotation != null) {multipartConfigElement =new MultipartConfigElement(annotation);}}// Special handling for ContainerServlet instances// Note: The InstanceManager checks if the application is permitted//to load ContainerServletsif (servlet instanceof ContainerServlet) {((ContainerServlet) servlet).setWrapper(this);}classLoadTime=(int) (System.currentTimeMillis() -t1);if (servlet instanceof SingleThreadModel) {if (instancePool == null) {instancePool = new Stack<>();}singleThreadModel = true;}initServlet(servlet);fireContainerEvent("load", this);loadTime=System.currentTimeMillis() -t1;} finally {if (swallowOutput) {String log = SystemLogHandler.stopCapture();if (log != null && log.length() > 0) {if (getServletContext() != null) {getServletContext().log(log);} else {out.println(log);}}}}retu servlet;}

  我们看到该方法是同步的(synchronized修饰符)。先看上面标黄的第一行,如果实现了之前说到的SigleThreadModel接口,那么这里的singleThreadModel就是true,就不会因为有Servlet实例而返回原有的Servlet了。但若反之,就返回原有的Servlet实例,符合单例的定义。

  第二处标黄说明Tomcat是通过反射来实例化Servlet的。它先根据web.xml(或者起相同作用的@WebServlet)找到ServletClass的全路径类名,然后通过类加载器得到Class对象,由Class对象取到构造器,通过构造器实例化ServletClass。

  最后看下例子:

package com.wlf.demo.servlet;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.PrintWriter;/** * 一个servlet的demo */@WebServlet(urlPattes = {"/hello","/world"})public class DemoServlet extends HttpServlet{// 全局变量,多线程情况下将被改写private String globalVariable = "";public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {// 后来的线程会覆盖前面的线程的值globalVariable = request.getParameter("param");// 给界面点击争取时间try{Thread.sleep(5000);}catch (InterruptedException e){e.printStackTrace();}// 页面输出PrintWriter pw = response.getWriter();pw.println("<HTML>");pw.println("<HEAD>");pw.println("<title>Hello, world</title>");pw.println("<body>");pw.printf("<p>input: %s</p>", globalVariable);pw.println("</HEAD>");pw.println("</HTML>");}}

  我们启动tomcat,在浏览器打开两个页面分别访问,然后我先刷新hello页面,在它转圈圈时去刷新world页面,这时world的参数值就会覆盖hello的参数值:

Servlet是单例的吗? _ JavaClub全栈架构师技术笔记

 

   但如果我们给DemoServlet实现了SingleThreadModel:

public class DemoServlet extends HttpServlet implements SingleThreadModel

  重新编译、打包、部署、重启tomcat后,重试上面的例子,并发问题就不存在了。当然,既然官方不建议你使用单线程模式,那么我们还是别用了,毕竟性能比起多线程来就低很多了。而多线程的问题我们可以通过线程封闭(全局变量改为局部变量)和加同步锁(用到全局变量时增加synchronized代码块)等方法来解决。

来源链接:https://www.cnblogs.com/wuxun1997/p/10769115.html

版权声明:
1、JavaClub(https://www.javaclub.cn)以学习交流为目的,由作者投稿、网友推荐和小编整理收藏优秀的IT技术及相关内容,包括但不限于文字、图片、音频、视频、软件、程序等,其均来自互联网,本站不享有版权,版权归原作者所有。

2、本站提供的内容仅用于个人学习、研究或欣赏,以及其他非商业性或非盈利性用途,但同时应遵守著作权法及其他相关法律的规定,不得侵犯相关权利人及本网站的合法权利。
3、本网站内容原作者如不愿意在本网站刊登内容,请及时通知本站(javaclubcn@163.com),我们将第一时间核实后及时予以删除。





本文链接:https://www.javaclub.cn/server/112398.html

标签:Servlet
分享给朋友:

“Servlet是单例的吗?” 的相关文章

[Multimedia][TS]TS流的解析 2022年05月17日 20:41:26
素数的判断,以及素数的遍历 2022年05月21日 11:41:39
如何学习一门语言 2022年05月23日 21:03:30
python中对切片的理解 2022年05月23日 21:57:45
备战5月软考 2022年05月23日 22:39:11
Python快速学习09: 函数的参数 2022年05月28日 23:16:03