• 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.
         * @return 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 pool
            if (!singleThreadModel && (instance != null))
                return instance;
    
            PrintStream out = System.out;
            if (swallowOutput) {
                SystemLogHandler.startCapture();
            }
    
            Servlet servlet;
            try {
                long t1=System.currentTimeMillis();
                // Complain if no servlet class has been specified
                if (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 ClassLoader
                    throw 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=36630
                    if(log.isDebugEnabled()) {
                        log.debug(sm.getString("standardWrapper.instantiate", servletClass), e);
                    }
    
                    // Restore the context ClassLoader
                    throw 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 ContainerServlets
                if (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);
                        }
                    }
                }
            }
            return 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(urlPatterns = {"/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的参数值:

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

    public class DemoServlet extends HttpServlet implements SingleThreadModel

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

  • 相关阅读:
    MYSQL进阶学习笔记十七:MySQL定期维护!(视频序号:进阶_36)
    MYSQL进阶学习笔记十六:MySQL 监控!(视频序号:进阶_35)
    MYSQL进阶学习笔记十五:MySQL 的账号权限赋予!(视频序号:进阶_33,34)
    MYSQL进阶学习笔记十四:MySQL 应用程序优化!(视频序号:进阶_32)
    学习布局
    接触IT的第一天
    分布视图分页
    单例模式
    js获取URL地址
    View视图传json格式数据到Js
  • 原文地址:https://www.cnblogs.com/wuxun1997/p/10769115.html
Copyright © 2020-2023  润新知