java 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.

*@returnthe loaded Servlet instance

*@throwsServletException for a Servlet load error*/

public synchronized Servlet loadServlet() throwsServletException {//Nothing to do if we already have an instance or an instance pool

if (!singleThreadModel && (instance != null))returninstance;

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 newServletException

(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 newServletException

(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 newServletException

(sm.getString("standardWrapper.instantiate", servletClass), e);

}if (multipartConfigElement == null) {

MultipartConfig annotation=servlet.getClass().getAnnotation(MultipartConfig.class);if (annotation != null) {

multipartConfigElement=

newMultipartConfigElement(annotation);

}

}//Special handling for ContainerServlet instances//Note: The InstanceManager checks if the application is permitted//to load ContainerServlets

if (servlet instanceofContainerServlet) {

((ContainerServlet) servlet).setWrapper(this);

}

classLoadTime=(int) (System.currentTimeMillis() -t1);if (servlet instanceofSingleThreadModel) {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);

}

}

}

}returnservlet;

}

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

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

最后看下例子:

packagecom.wlf.demo.servlet;importjavax.servlet.annotation.WebServlet;importjavax.servlet.http.HttpServlet;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjava.io.IOException;importjava.io.PrintWriter;/*** 一个servlet的demo*/@WebServlet(urlPatterns= {"/hello","/world"})public class DemoServlet extendsHttpServlet{//全局变量,多线程情况下将被改写

private String globalVariable = "";public void doGet(HttpServletRequest request, HttpServletResponse response) throwsIOException {//后来的线程会覆盖前面的线程的值

globalVariable = request.getParameter("param");//给界面点击争取时间

try{

Thread.sleep(5000);

}catch(InterruptedException e)

{

e.printStackTrace();

}//页面输出

PrintWriter pw =response.getWriter();

pw.println("");

pw.println("

");

pw.println("

Hello, world");

pw.println("

");

pw.printf("

input: %s

", globalVariable);

pw.println("");

pw.println("");

}

}

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

ebadcd16db96f81e34962403cc29c0e0.png

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

public class DemoServlet extends HttpServlet implements SingleThreadModel

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值